Merge branch 'master' into feature_user-session-timeout

# Conflicts:
#	sql/ruoyi-vue-pro.sql
pull/2/head
Lyon 2021-03-09 10:07:08 +08:00
commit bcb6a68b80
40 changed files with 631 additions and 258 deletions

View File

@ -43,11 +43,12 @@
1. 幂等组件:基于 Redis 实现幂等组件,解决重复请求问题 1. 幂等组件:基于 Redis 实现幂等组件,解决重复请求问题
1. 服务保障:基于 Resilience4j 实现服务的稳定性,包括限流、熔断等功能 1. 服务保障:基于 Resilience4j 实现服务的稳定性,包括限流、熔断等功能
1. 日志服务:轻量级日志中心,查看远程服务器的日志 1. 日志服务:轻量级日志中心,查看远程服务器的日志
1. 单元测试:基于 JUnit + Mockito 实现单元测试,保证功能的正确性、代码的质量等
### 研发工具 ### 研发工具
1. 表单构建:拖动表单元素生成相应的 HTML 代码 1. 表单构建:拖动表单元素生成相应的 HTML 代码
1. 代码生成前后端代码的生成Java、Vue、SQL支持 CRUD 下载 1. 代码生成前后端代码的生成Java、Vue、SQL、单元测试),支持 CRUD 下载
1. 系统接口:基于 Swagger 自动生成相关的 RESTful API 接口文档 1. 系统接口:基于 Swagger 自动生成相关的 RESTful API 接口文档
1. 数据库文档:基于 Screw 自动生成数据库文档 1. 数据库文档:基于 Screw 自动生成数据库文档
@ -83,7 +84,9 @@
| [Spring Boot Admin](https://github.com/skywalking) | Spring Boot 监控平台 | 8.6.0 | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao) | | [Spring Boot Admin](https://github.com/skywalking) | Spring Boot 监控平台 | 8.6.0 | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao) |
| [Jackson](https://github.com/FasterXML/jackson) | JSON 工具库 | 2.11.4 | | | [Jackson](https://github.com/FasterXML/jackson) | JSON 工具库 | 2.11.4 | |
| [MapStruct](https://mapstruct.org/) | Java Bean 转换 | 1.4.1 | [文档](http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao) | | [MapStruct](https://mapstruct.org/) | Java Bean 转换 | 1.4.1 | [文档](http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao) |
| [Lombok](https://projectlombok.org/) | 消除冗长的 Java 代码| 1.16.14 | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao) | | [Lombok](https://projectlombok.org/) | 消除冗长的 Java 代码 | 1.16.14 | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao) |
| [JUnit](https://junit.org/junit5/) | Java 单元测试框架 | 5.7.0 | - |
| [Mockito](https://github.com/mockito/mockito) | Java Mock 框架 | 3.6.28 | - |
**前端** **前端**
@ -125,7 +128,7 @@
</tr> </tr>
<tr> <tr>
<td><img src="https://oscimg.oschina.net/oscnet/b6115bc8c31de52951982e509930b20684a.jpg"/></td> <td><img src="https://oscimg.oschina.net/oscnet/b6115bc8c31de52951982e509930b20684a.jpg"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-6d73c2140ce694e3de4c05035fdc1868d4c.png"/></td> <td> - </td>
</tr> </tr>
</table> </table>

12
pom.xml
View File

@ -2,7 +2,7 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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"> 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> <modelVersion>4.0.0</modelVersion>
<groupId>cn.iocoder</groupId> <groupId>cn.iocoder</groupId>
<artifactId>dashboard</artifactId> <artifactId>dashboard</artifactId>
@ -25,6 +25,7 @@
<spring.boot.version>2.4.2</spring.boot.version> <spring.boot.version>2.4.2</spring.boot.version>
<!-- Web 相关 --> <!-- Web 相关 -->
<knife4j.version>3.0.2</knife4j.version> <knife4j.version>3.0.2</knife4j.version>
<swagger-annotations.version>1.5.22</swagger-annotations.version>
<!-- DB 相关 --> <!-- DB 相关 -->
<mysql-connector-java.version>5.1.46</mysql-connector-java.version> <mysql-connector-java.version>5.1.46</mysql-connector-java.version>
<druid.version>1.2.4</druid.version> <druid.version>1.2.4</druid.version>
@ -104,8 +105,17 @@
<artifactId>guava</artifactId> <artifactId>guava</artifactId>
<groupId>com.google.guava</groupId> <groupId>com.google.guava</groupId>
</exclusion> </exclusion>
<exclusion>
<artifactId>swagger-annotations</artifactId>
<groupId>io.swagger</groupId>
</exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>${swagger-annotations.version}</version>
</dependency>
<!-- DB 相关 --> <!-- DB 相关 -->
<dependency> <dependency>

View File

@ -11,7 +11,7 @@
Target Server Version : 50718 Target Server Version : 50718
File Encoding : 65001 File Encoding : 65001
Date: 07/03/2021 00:43:34 Date: 08/03/2021 00:50:29
*/ */
SET NAMES utf8mb4; SET NAMES utf8mb4;
@ -43,7 +43,7 @@ CREATE TABLE `inf_api_access_log` (
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1822 DEFAULT CHARSET=utf8mb4 COMMENT='API 访问日志表'; ) ENGINE=InnoDB AUTO_INCREMENT=1850 DEFAULT CHARSET=utf8mb4 COMMENT='API 访问日志表';
-- ---------------------------- -- ----------------------------
-- Records of inf_api_access_log -- Records of inf_api_access_log
@ -175,7 +175,7 @@ CREATE TABLE `inf_job_log` (
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=4458 DEFAULT CHARSET=utf8mb4 COMMENT='定时任务日志表'; ) ENGINE=InnoDB AUTO_INCREMENT=4477 DEFAULT CHARSET=utf8mb4 COMMENT='定时任务日志表';
-- ---------------------------- -- ----------------------------
-- Records of inf_job_log -- Records of inf_job_log
@ -579,7 +579,7 @@ CREATE TABLE `sys_operate_log` (
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COMMENT='操作日志记录'; ) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COMMENT='操作日志记录';
-- ---------------------------- -- ----------------------------
-- Records of sys_operate_log -- Records of sys_operate_log
@ -977,13 +977,14 @@ INSERT INTO `sys_user_session` VALUES ('e3ad1ef8b9aa4b329855b29c7b372e8f', 1, '1
INSERT INTO `sys_user_session` VALUES ('ea0d48776db84da4ac0f4c2adf62c366', 1, '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36', '2021-02-08 07:02:03', '', '2021-02-08 07:02:03', '', '2021-03-07 14:58:55', b'0'); INSERT INTO `sys_user_session` VALUES ('ea0d48776db84da4ac0f4c2adf62c366', 1, '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36', '2021-02-08 07:02:03', '', '2021-02-08 07:02:03', '', '2021-03-07 14:58:55', b'0');
INSERT INTO `sys_user_session` VALUES ('f881f7dc67d04cd29574657fdde32a62', 1, '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36', '2021-02-05 08:53:20', '', '2021-02-05 08:53:20', '', '2021-03-07 14:58:55', b'0'); INSERT INTO `sys_user_session` VALUES ('f881f7dc67d04cd29574657fdde32a62', 1, '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36', '2021-02-05 08:53:20', '', '2021-02-05 08:53:20', '', '2021-03-07 14:58:55', b'0');
COMMIT; COMMIT;
-- ----------------------------null,
-- Table structure for tool_codegen_columnnull, -- ----------------------------
-- ----------------------------null, -- Table structure for tool_codegen_column
DROP TABLE IF EXISTS `tool_codegen_column`;null, -- ----------------------------
CREATE TABLE `tool_codegen_column` (null, DROP TABLE IF EXISTS `tool_codegen_column`;
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号',null, CREATE TABLE `tool_codegen_column` (
`table_id` bigint(20) NOT NULL COMMENT '表编号',null, `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
`table_id` bigint(20) NOT NULL COMMENT '表编号',
`column_name` varchar(200) NOT NULL COMMENT '字段名', `column_name` varchar(200) NOT NULL COMMENT '字段名',
`column_type` varchar(100) NOT NULL COMMENT '字段类型', `column_type` varchar(100) NOT NULL COMMENT '字段类型',
`column_comment` varchar(500) NOT NULL COMMENT '字段描述', `column_comment` varchar(500) NOT NULL COMMENT '字段描述',
@ -1007,7 +1008,7 @@ CREATE TABLE `tool_codegen_column` (null,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=369 DEFAULT CHARSET=utf8mb4 COMMENT='代码生成表字段定义'; ) ENGINE=InnoDB AUTO_INCREMENT=381 DEFAULT CHARSET=utf8mb4 COMMENT='代码生成表字段定义';
-- ---------------------------- -- ----------------------------
-- Records of tool_codegen_column -- Records of tool_codegen_column
@ -1149,6 +1150,18 @@ INSERT INTO `tool_codegen_column` VALUES (365, 29, 'create_time', 'datetime', '
INSERT INTO `tool_codegen_column` VALUES (366, 29, 'update_by', 'varchar(64)', '更新者', b'1', b'0', '0', 8, 'String', 'updateBy', '', NULL, b'0', b'0', b'0', '=', b'0', 'input', '', '2021-03-06 03:52:57', '', '2021-03-06 03:52:57', b'0'); INSERT INTO `tool_codegen_column` VALUES (366, 29, 'update_by', 'varchar(64)', '更新者', b'1', b'0', '0', 8, 'String', 'updateBy', '', NULL, b'0', b'0', b'0', '=', b'0', 'input', '', '2021-03-06 03:52:57', '', '2021-03-06 03:52:57', b'0');
INSERT INTO `tool_codegen_column` VALUES (367, 29, 'update_time', 'datetime', '更新时间', b'0', b'0', '0', 9, 'Date', 'updateTime', '', NULL, b'0', b'0', b'0', 'BETWEEN', b'0', 'datetime', '', '2021-03-06 03:52:57', '', '2021-03-06 03:52:57', b'0'); INSERT INTO `tool_codegen_column` VALUES (367, 29, 'update_time', 'datetime', '更新时间', b'0', b'0', '0', 9, 'Date', 'updateTime', '', NULL, b'0', b'0', b'0', 'BETWEEN', b'0', 'datetime', '', '2021-03-06 03:52:57', '', '2021-03-06 03:52:57', b'0');
INSERT INTO `tool_codegen_column` VALUES (368, 29, 'deleted', 'bit(1)', '是否删除', b'0', b'0', '0', 10, 'Boolean', 'deleted', '', NULL, b'0', b'0', b'0', '=', b'0', 'radio', '', '2021-03-06 03:52:57', '', '2021-03-06 03:52:57', b'0'); INSERT INTO `tool_codegen_column` VALUES (368, 29, 'deleted', 'bit(1)', '是否删除', b'0', b'0', '0', 10, 'Boolean', 'deleted', '', NULL, b'0', b'0', b'0', '=', b'0', 'radio', '', '2021-03-06 03:52:57', '', '2021-03-06 03:52:57', b'0');
INSERT INTO `tool_codegen_column` VALUES (369, 30, 'id', 'bigint(20)', '字典编码', b'0', b'1', '1', 1, 'Long', 'id', '', NULL, b'0', b'1', b'0', '=', b'1', 'input', '', '2021-03-06 06:48:28', '', '2021-03-06 06:48:28', b'0');
INSERT INTO `tool_codegen_column` VALUES (370, 30, 'sort', 'int(4)', '字典排序', b'0', b'0', '0', 2, 'Integer', 'sort', '', NULL, b'1', b'1', b'0', '=', b'1', 'input', '', '2021-03-06 06:48:28', '', '2021-03-06 06:50:38', b'0');
INSERT INTO `tool_codegen_column` VALUES (371, 30, 'label', 'varchar(100)', '字典标签', b'0', b'0', '0', 3, 'String', 'label', '', NULL, b'1', b'1', b'1', 'LIKE', b'1', 'input', '', '2021-03-06 06:48:28', '', '2021-03-06 06:50:38', b'0');
INSERT INTO `tool_codegen_column` VALUES (372, 30, 'value', 'varchar(100)', '字典键值', b'0', b'0', '0', 4, 'String', 'value', '', NULL, b'1', b'1', b'0', '=', b'1', 'input', '', '2021-03-06 06:48:28', '', '2021-03-06 06:50:38', b'0');
INSERT INTO `tool_codegen_column` VALUES (373, 30, 'dict_type', 'varchar(100)', '字典类型', b'0', b'0', '0', 5, 'String', 'dictType', '', NULL, b'1', b'1', b'1', 'LIKE', b'1', 'select', '', '2021-03-06 06:48:28', '', '2021-03-06 06:50:38', b'0');
INSERT INTO `tool_codegen_column` VALUES (374, 30, 'status', 'tinyint(4)', '状态0正常 1停用', b'0', b'0', '0', 6, 'Integer', 'status', '', NULL, b'1', b'1', b'1', 'LIKE', b'1', 'radio', '', '2021-03-06 06:48:28', '', '2021-03-06 06:50:38', b'0');
INSERT INTO `tool_codegen_column` VALUES (375, 30, 'remark', 'varchar(500)', '备注', b'1', b'0', '0', 7, 'String', 'remark', '', NULL, b'1', b'1', b'0', '=', b'1', 'input', '', '2021-03-06 06:48:28', '', '2021-03-06 06:50:38', b'0');
INSERT INTO `tool_codegen_column` VALUES (376, 30, 'create_by', 'varchar(64)', '创建者', b'1', b'0', '0', 8, 'String', 'createBy', '', NULL, b'0', b'0', b'0', '=', b'0', 'input', '', '2021-03-06 06:48:28', '', '2021-03-06 06:48:28', b'0');
INSERT INTO `tool_codegen_column` VALUES (377, 30, 'create_time', 'datetime', '创建时间', b'0', b'0', '0', 9, 'Date', 'createTime', '', NULL, b'0', b'0', b'1', 'BETWEEN', b'1', 'datetime', '', '2021-03-06 06:48:28', '', '2021-03-06 06:48:28', b'0');
INSERT INTO `tool_codegen_column` VALUES (378, 30, 'update_by', 'varchar(64)', '更新者', b'1', b'0', '0', 10, 'String', 'updateBy', '', NULL, b'0', b'0', b'0', '=', b'0', 'input', '', '2021-03-06 06:48:28', '', '2021-03-06 06:48:28', b'0');
INSERT INTO `tool_codegen_column` VALUES (379, 30, 'update_time', 'datetime', '更新时间', b'0', b'0', '0', 11, 'Date', 'updateTime', '', NULL, b'0', b'0', b'0', 'BETWEEN', b'0', 'datetime', '', '2021-03-06 06:48:28', '', '2021-03-06 06:48:28', b'0');
INSERT INTO `tool_codegen_column` VALUES (380, 30, 'deleted', 'bit(1)', '是否删除', b'0', b'0', '0', 12, 'Boolean', 'deleted', '', NULL, b'0', b'0', b'0', '=', b'0', 'radio', '', '2021-03-06 06:48:28', '', '2021-03-06 06:48:28', b'0');
COMMIT; COMMIT;
-- ---------------------------- -- ----------------------------
@ -1174,7 +1187,7 @@ CREATE TABLE `tool_codegen_table` (
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8mb4 COMMENT='代码生成表定义'; ) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8mb4 COMMENT='代码生成表定义';
-- ---------------------------- -- ----------------------------
-- Records of tool_codegen_table -- Records of tool_codegen_table
@ -1190,6 +1203,7 @@ INSERT INTO `tool_codegen_table` VALUES (26, 1, 'inf_api_access_log', 'API 访
INSERT INTO `tool_codegen_table` VALUES (27, 1, 'inf_api_error_log', 'API 错误日志', NULL, 'infra', 'apiErrorLog', 'InfApiErrorLog', 'API 错误日志', '芋道源码', 1, 1083, '', '2021-02-26 06:54:49', '', '2021-02-26 07:53:03', b'0'); INSERT INTO `tool_codegen_table` VALUES (27, 1, 'inf_api_error_log', 'API 错误日志', NULL, 'infra', 'apiErrorLog', 'InfApiErrorLog', 'API 错误日志', '芋道源码', 1, 1083, '', '2021-02-26 06:54:49', '', '2021-02-26 07:53:03', b'0');
INSERT INTO `tool_codegen_table` VALUES (28, 1, 'sys_dict_type', '字典类型表', NULL, 'system', 'dictType', 'SysDictType', '字典类型', '芋艿', 1, NULL, '', '2021-03-06 03:45:55', '', '2021-03-06 03:51:02', b'1'); INSERT INTO `tool_codegen_table` VALUES (28, 1, 'sys_dict_type', '字典类型表', NULL, 'system', 'dictType', 'SysDictType', '字典类型', '芋艿', 1, NULL, '', '2021-03-06 03:45:55', '', '2021-03-06 03:51:02', b'1');
INSERT INTO `tool_codegen_table` VALUES (29, 1, 'sys_dict_type', '字典类型表', NULL, 'system', 'dict', 'SysDictType', '字典类型', '芋艿', 1, NULL, '', '2021-03-06 03:52:57', '', '2021-03-06 04:03:52', b'0'); INSERT INTO `tool_codegen_table` VALUES (29, 1, 'sys_dict_type', '字典类型表', NULL, 'system', 'dict', 'SysDictType', '字典类型', '芋艿', 1, NULL, '', '2021-03-06 03:52:57', '', '2021-03-06 04:03:52', b'0');
INSERT INTO `tool_codegen_table` VALUES (30, 1, 'sys_dict_data', '字典数据表', NULL, 'system', 'type', 'SysDictData', '字典数据', '芋道源码', 1, NULL, '', '2021-03-06 06:48:28', '', '2021-03-06 06:50:47', b'0');
COMMIT; COMMIT;
-- ---------------------------- -- ----------------------------

View File

@ -13,7 +13,8 @@ import org.springframework.context.annotation.Configuration;
* @author * @author
*/ */
@Configuration @Configuration
@MapperScan(value = "${yudao.info.base-package}", annotationClass = Mapper.class) @MapperScan(value = "${yudao.info.base-package}", annotationClass = Mapper.class,
lazyInitialization = "${mybatis.lazy-initialization:false}") // Mapper 懒加载,目前仅用于单元测试
public class MybatisConfiguration { public class MybatisConfiguration {
@Bean @Bean

View File

@ -21,13 +21,13 @@ public class BaseDO implements Serializable {
*/ */
private Date updateTime; private Date updateTime;
/** /**
* TODO *
*/ */
private String createBy; private String creator;
/** /**
* TODO *
*/ */
private String updateBy; private String updater;
/** /**
* *
*/ */

View File

@ -0,0 +1,65 @@
package cn.iocoder.dashboard.framework.mybatis.core.handle;
import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.dashboard.framework.security.core.LoginUser;
import cn.iocoder.dashboard.framework.security.core.util.SecurityFrameworkUtils;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.Objects;
/**
*
*
*
*
* @author hexiaowu
*/
@Component
public class DefaultDBFieldHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BaseDO) {
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
BaseDO baseDO = (BaseDO) metaObject.getOriginalObject();
Date current = new Date();
// 创建时间为空,则以当前时间为插入时间
if (Objects.isNull(baseDO.getCreateTime())) {
baseDO.setCreateTime(current);
}
// 更新时间为空,则以当前时间为更新时间
if (Objects.isNull(baseDO.getUpdateTime())) {
baseDO.setUpdateTime(current);
}
// 当前登录用户不为空,创建人为空,则当前登录用户为创建人
if (Objects.nonNull(loginUser) && Objects.isNull(baseDO.getCreator())) {
baseDO.setCreator(loginUser.getId().toString());
}
// 当前登录用户不为空,更新人为空,则当前登录用户为更新人
if (Objects.nonNull(loginUser) && Objects.isNull(baseDO.getUpdater())) {
baseDO.setUpdater(loginUser.getId().toString());
}
}
}
@Override
public void updateFill(MetaObject metaObject) {
Object modifyTime = getFieldValByName("updateTime", metaObject);
Object modifier = getFieldValByName("updater", metaObject);
// 获取登录用户信息
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
// 更新时间为空,则以当前时间为更新时间
if (Objects.isNull(modifyTime)) {
setFieldValByName("updateTime", new Date(), metaObject);
}
// 当前登录用户不为空,更新人为空,则当前登录用户为更新人
if (Objects.nonNull(loginUser) && Objects.isNull(modifier)) {
setFieldValByName("updater", loginUser.getId(), metaObject);
}
}
}

View File

@ -28,6 +28,10 @@ public interface BaseMapperX<T> extends BaseMapper<T> {
return selectOne(new QueryWrapper<T>().eq(field, value)); return selectOne(new QueryWrapper<T>().eq(field, value));
} }
default Integer selectCount(String field, Object value) {
return selectCount(new QueryWrapper<T>().eq(field, value));
}
default List<T> selectList() { default List<T> selectList() {
return selectList(new QueryWrapper<>()); return selectList(new QueryWrapper<>());
} }

View File

@ -1 +0,0 @@
<http://www.iocoder.cn/Spring-Boot/Spring-Security/?github>

View File

@ -0,0 +1 @@
<https://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao>

View File

@ -152,7 +152,7 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
.anyRequest().authenticated() .anyRequest().authenticated()
.and() .and()
.headers().frameOptions().disable(); .headers().frameOptions().disable();
httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler); httpSecurity.logout().logoutUrl(webProperties.getApiPrefix() + "/logout").logoutSuccessHandler(logoutSuccessHandler);
// 添加 JWT Filter // 添加 JWT Filter
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
} }

View File

@ -1,6 +1,7 @@
package cn.iocoder.dashboard.framework.security.core.handler; package cn.iocoder.dashboard.framework.security.core.handler;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.dashboard.common.pojo.CommonResult;
import cn.iocoder.dashboard.framework.security.config.SecurityProperties; import cn.iocoder.dashboard.framework.security.config.SecurityProperties;
import cn.iocoder.dashboard.framework.security.core.service.SecurityAuthFrameworkService; import cn.iocoder.dashboard.framework.security.core.service.SecurityAuthFrameworkService;
import cn.iocoder.dashboard.framework.security.core.util.SecurityFrameworkUtils; import cn.iocoder.dashboard.framework.security.core.util.SecurityFrameworkUtils;
@ -36,6 +37,6 @@ public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
securityFrameworkService.logout(token); securityFrameworkService.logout(token);
} }
// 返回成功 // 返回成功
ServletUtils.writeJSON(response, null); ServletUtils.writeJSON(response, CommonResult.success(null));
} }
} }

View File

@ -2,7 +2,10 @@ package cn.iocoder.dashboard.framework.security.core.util;
import cn.iocoder.dashboard.framework.security.core.LoginUser; import cn.iocoder.dashboard.framework.security.core.LoginUser;
import cn.iocoder.dashboard.framework.web.core.util.WebFrameworkUtils; import cn.iocoder.dashboard.framework.web.core.util.WebFrameworkUtils;
import org.springframework.lang.Nullable;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -40,9 +43,20 @@ public class SecurityFrameworkUtils {
/** /**
* *
*
* @return
*/ */
@Nullable
public static LoginUser getLoginUser() { public static LoginUser getLoginUser() {
return (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); SecurityContext context = SecurityContextHolder.getContext();
if (context == null) {
return null;
}
Authentication authentication = context.getAuthentication();
if (authentication == null) {
return null;
}
return (LoginUser) authentication.getPrincipal();
} }
/** /**
@ -50,8 +64,10 @@ public class SecurityFrameworkUtils {
* *
* @return * @return
*/ */
@Nullable
public static Long getLoginUserId() { public static Long getLoginUserId() {
return getLoginUser().getId(); LoginUser loginUser = getLoginUser();
return loginUser != null ? loginUser.getId() : null;
} }
/** /**
@ -59,8 +75,10 @@ public class SecurityFrameworkUtils {
* *
* @return * @return
*/ */
@Nullable
public static Set<Long> getLoginUserRoleIds() { public static Set<Long> getLoginUserRoleIds() {
return getLoginUser().getRoleIds(); LoginUser loginUser = getLoginUser();
return loginUser != null ? loginUser.getRoleIds() : null;
} }
/** /**

View File

@ -10,15 +10,17 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.*; import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.Contact;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.service.SecurityScheme;
import springfox.documentation.spi.DocumentationType; import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext; import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2; import springfox.documentation.swagger2.annotations.EnableSwagger2;
import springfox.documentation.service.ApiKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;

View File

@ -27,9 +27,10 @@ public class WebConfiguration implements WebMvcConfigurer {
@Override @Override
public void configurePathMatch(PathMatchConfigurer configurer) { public void configurePathMatch(PathMatchConfigurer configurer) {
// 设置 API 前缀,仅仅匹配 controller 包下的
configurer.addPathPrefix(webProperties.getApiPrefix(), clazz -> configurer.addPathPrefix(webProperties.getApiPrefix(), clazz ->
clazz.isAnnotationPresent(RestController.class) clazz.isAnnotationPresent(RestController.class)
&& clazz.getPackage().getName().startsWith(webProperties.getControllerPackage())); && clazz.getPackage().getName().startsWith(webProperties.getControllerPackage())); // 仅仅匹配 controller 包
} }
// ========== Filter 相关 ========== // ========== Filter 相关 ==========

View File

@ -12,6 +12,7 @@ import cn.iocoder.dashboard.modules.infra.dal.dataobject.config.InfConfigDO;
import cn.iocoder.dashboard.modules.infra.enums.config.InfConfigTypeEnum; import cn.iocoder.dashboard.modules.infra.enums.config.InfConfigTypeEnum;
import cn.iocoder.dashboard.modules.infra.mq.producer.config.InfConfigProducer; import cn.iocoder.dashboard.modules.infra.mq.producer.config.InfConfigProducer;
import cn.iocoder.dashboard.modules.infra.service.config.InfConfigService; import cn.iocoder.dashboard.modules.infra.service.config.InfConfigService;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -99,7 +100,8 @@ public class InfConfigServiceImpl implements InfConfigService {
checkConfigKeyUnique(id, key); checkConfigKeyUnique(id, key);
} }
private InfConfigDO checkConfigExists(Long id) { @VisibleForTesting
public InfConfigDO checkConfigExists(Long id) {
if (id == null) { if (id == null) {
return null; return null;
} }
@ -110,7 +112,8 @@ public class InfConfigServiceImpl implements InfConfigService {
return config; return config;
} }
private void checkConfigKeyUnique(Long id, String key) { @VisibleForTesting
public void checkConfigKeyUnique(Long id, String key) {
InfConfigDO config = configMapper.selectByKey(key); InfConfigDO config = configMapper.selectByKey(key);
if (config == null) { if (config == null) {
return; return;

View File

@ -26,7 +26,7 @@ public class SysDeptBaseVO {
@ApiModelProperty(value = "显示顺序不能为空", required = true, example = "1024") @ApiModelProperty(value = "显示顺序不能为空", required = true, example = "1024")
@NotBlank(message = "显示顺序不能为空") @NotBlank(message = "显示顺序不能为空")
private String sort; private Integer sort;
@ApiModelProperty(value = "负责人", example = "芋道") @ApiModelProperty(value = "负责人", example = "芋道")
private String leader; private String leader;

View File

@ -25,7 +25,7 @@ public class SysPostBaseVO {
@ApiModelProperty(value = "显示顺序不能为空", required = true, example = "1024") @ApiModelProperty(value = "显示顺序不能为空", required = true, example = "1024")
@NotBlank(message = "显示顺序不能为空") @NotBlank(message = "显示顺序不能为空")
private String sort; private Integer sort;
@ApiModelProperty(value = "状态", required = true, example = "1", notes = "参见 SysCommonStatusEnum 枚举类") @ApiModelProperty(value = "状态", required = true, example = "1", notes = "参见 SysCommonStatusEnum 枚举类")
private Integer status; private Integer status;

View File

@ -23,7 +23,7 @@ public class SysPostExcelVO {
private String name; private String name;
@ExcelProperty("岗位排序") @ExcelProperty("岗位排序")
private String sort; private Integer sort;
@ExcelProperty(value = "状态", converter = DictConvert.class) @ExcelProperty(value = "状态", converter = DictConvert.class)
@DictFormat(SYS_COMMON_STATUS) @DictFormat(SYS_COMMON_STATUS)

View File

@ -29,7 +29,7 @@ public class SysMenuBaseVO {
@ApiModelProperty(value = "显示顺序不能为空", required = true, example = "1024") @ApiModelProperty(value = "显示顺序不能为空", required = true, example = "1024")
@NotBlank(message = "显示顺序不能为空") @NotBlank(message = "显示顺序不能为空")
private String sort; private Integer sort;
@ApiModelProperty(value = "父菜单 ID", required = true, example = "1024") @ApiModelProperty(value = "父菜单 ID", required = true, example = "1024")
@NotNull(message = "父菜单 ID 不能为空") @NotNull(message = "父菜单 ID 不能为空")

View File

@ -25,7 +25,7 @@ public class SysRoleBaseVO {
@ApiModelProperty(value = "显示顺序不能为空", required = true, example = "1024") @ApiModelProperty(value = "显示顺序不能为空", required = true, example = "1024")
@NotBlank(message = "显示顺序不能为空") @NotBlank(message = "显示顺序不能为空")
private String sort; private Integer sort;
@ApiModelProperty(value = "角色类型", required = true, example = "1", notes = "见 SysRoleTypeEnum 枚举") @ApiModelProperty(value = "角色类型", required = true, example = "1", notes = "见 SysRoleTypeEnum 枚举")
private Integer type; private Integer type;

View File

@ -35,7 +35,7 @@ public class SysDeptDO extends BaseDO {
/** /**
* *
*/ */
private String sort; private Integer sort;
/** /**
* *
*/ */

View File

@ -34,7 +34,7 @@ public class SysPostDO extends BaseDO {
/** /**
* *
*/ */
private String sort; private Integer sort;
/** /**
* *
* *

View File

@ -49,7 +49,7 @@ public class SysMenuDO extends BaseDO {
/** /**
* *
*/ */
private String sort; private Integer sort;
/** /**
* ID * ID
*/ */

View File

@ -15,13 +15,13 @@ import java.util.List;
@Mapper @Mapper
public interface SysDictDataMapper extends BaseMapperX<SysDictDataDO> { public interface SysDictDataMapper extends BaseMapperX<SysDictDataDO> {
default SysDictDataDO selectByDictTypeAndLabel(String dictType, String label) { default SysDictDataDO selectByDictTypeAndValue(String dictType, String value) {
return selectOne(new QueryWrapper<SysDictDataDO>().eq("dict_type", dictType) return selectOne(new QueryWrapper<SysDictDataDO>().eq("dict_type", dictType)
.eq("label", label)); .eq("value", value));
} }
default int selectCountByDictType(String dictType) { default int selectCountByDictType(String dictType) {
return selectCount(new QueryWrapper<SysDictDataDO>().eq("dict_type", dictType)); return selectCount("dict_type", dictType);
} }
default PageResult<SysDictDataDO> selectPage(SysDictDataPageReqVO reqVO) { default PageResult<SysDictDataDO> selectPage(SysDictDataPageReqVO reqVO) {

View File

@ -160,7 +160,26 @@ public class SysAuthServiceImpl implements SysAuthService {
@Override @Override
public void logout(String token) { public void logout(String token) {
// AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, "退出成功")); TODO 需要搞一搞 // 查询用户信息
LoginUser loginUser = userSessionService.getLoginUser(token);
if (loginUser == null) {
return;
}
// 删除 session
userSessionService.deleteUserSession(token);
// 记录登出日子和
this.createLogoutLog(loginUser.getUsername());
}
private void createLogoutLog(String username) {
SysLoginLogCreateReqVO reqVO = new SysLoginLogCreateReqVO();
reqVO.setLogType(SysLoginLogTypeEnum.LOGOUT_SELF.getType());
reqVO.setTraceId(TracerUtils.getTraceId());
reqVO.setUsername(username);
reqVO.setUserAgent(ServletUtils.getUserAgent());
reqVO.setUserIp(ServletUtils.getClientIP());
reqVO.setResult(SysLoginResultEnum.SUCCESS.getResult());
loginLogService.createLoginLog(reqVO);
} }
@Override @Override

View File

@ -2,7 +2,6 @@ package cn.iocoder.dashboard.modules.system.service.dict.impl;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.iocoder.dashboard.common.enums.CommonStatusEnum; import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.dashboard.common.pojo.PageResult; import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.dashboard.modules.system.controller.dict.vo.data.SysDictDataCreateReqVO; import cn.iocoder.dashboard.modules.system.controller.dict.vo.data.SysDictDataCreateReqVO;
@ -10,12 +9,13 @@ import cn.iocoder.dashboard.modules.system.controller.dict.vo.data.SysDictDataEx
import cn.iocoder.dashboard.modules.system.controller.dict.vo.data.SysDictDataPageReqVO; import cn.iocoder.dashboard.modules.system.controller.dict.vo.data.SysDictDataPageReqVO;
import cn.iocoder.dashboard.modules.system.controller.dict.vo.data.SysDictDataUpdateReqVO; import cn.iocoder.dashboard.modules.system.controller.dict.vo.data.SysDictDataUpdateReqVO;
import cn.iocoder.dashboard.modules.system.convert.dict.SysDictDataConvert; import cn.iocoder.dashboard.modules.system.convert.dict.SysDictDataConvert;
import cn.iocoder.dashboard.modules.system.dal.mysql.dict.SysDictDataMapper;
import cn.iocoder.dashboard.modules.system.dal.dataobject.dict.SysDictDataDO; import cn.iocoder.dashboard.modules.system.dal.dataobject.dict.SysDictDataDO;
import cn.iocoder.dashboard.modules.system.dal.dataobject.dict.SysDictTypeDO; import cn.iocoder.dashboard.modules.system.dal.dataobject.dict.SysDictTypeDO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dict.SysDictDataMapper;
import cn.iocoder.dashboard.modules.system.mq.producer.dict.SysDictDataProducer; import cn.iocoder.dashboard.modules.system.mq.producer.dict.SysDictDataProducer;
import cn.iocoder.dashboard.modules.system.service.dict.SysDictDataService; import cn.iocoder.dashboard.modules.system.service.dict.SysDictDataService;
import cn.iocoder.dashboard.modules.system.service.dict.SysDictTypeService; import cn.iocoder.dashboard.modules.system.service.dict.SysDictTypeService;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableTable; import com.google.common.collect.ImmutableTable;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
@ -28,6 +28,7 @@ import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import static cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*; import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
/** /**
@ -156,7 +157,7 @@ public class SysDictDataServiceImpl implements SysDictDataService {
@Override @Override
public Long createDictData(SysDictDataCreateReqVO reqVO) { public Long createDictData(SysDictDataCreateReqVO reqVO) {
// 校验正确性 // 校验正确性
this.checkCreateOrUpdate(null, reqVO.getLabel(), reqVO.getDictType()); this.checkCreateOrUpdate(null, reqVO.getValue(), reqVO.getDictType());
// 插入字典类型 // 插入字典类型
SysDictDataDO dictData = SysDictDataConvert.INSTANCE.convert(reqVO); SysDictDataDO dictData = SysDictDataConvert.INSTANCE.convert(reqVO);
dictDataMapper.insert(dictData); dictDataMapper.insert(dictData);
@ -168,7 +169,7 @@ public class SysDictDataServiceImpl implements SysDictDataService {
@Override @Override
public void updateDictData(SysDictDataUpdateReqVO reqVO) { public void updateDictData(SysDictDataUpdateReqVO reqVO) {
// 校验正确性 // 校验正确性
this.checkCreateOrUpdate(reqVO.getId(), reqVO.getLabel(), reqVO.getDictType()); this.checkCreateOrUpdate(reqVO.getId(), reqVO.getValue(), reqVO.getDictType());
// 更新字典类型 // 更新字典类型
SysDictDataDO updateObj = SysDictDataConvert.INSTANCE.convert(reqVO); SysDictDataDO updateObj = SysDictDataConvert.INSTANCE.convert(reqVO);
dictDataMapper.updateById(updateObj); dictDataMapper.updateById(updateObj);
@ -191,46 +192,49 @@ public class SysDictDataServiceImpl implements SysDictDataService {
return dictDataMapper.selectCountByDictType(dictType); return dictDataMapper.selectCountByDictType(dictType);
} }
private void checkCreateOrUpdate(Long id, String label, String dictType) { private void checkCreateOrUpdate(Long id, String value, String dictType) {
// 校验自己存在 // 校验自己存在
checkDictDataExists(id); checkDictDataExists(id);
// 校验字典类型有效 // 校验字典类型有效
checkDictTypeValid(dictType); checkDictTypeValid(dictType);
// 校验字典数据的值的唯一性 // 校验字典数据的值的唯一性
checkDictDataValueUnique(id, dictType, label); checkDictDataValueUnique(id, dictType, value);
} }
private void checkDictDataValueUnique(Long id, String dictType, String label) { @VisibleForTesting
SysDictDataDO dictData = dictDataMapper.selectByDictTypeAndLabel(dictType, label); public void checkDictDataValueUnique(Long id, String dictType, String value) {
SysDictDataDO dictData = dictDataMapper.selectByDictTypeAndValue(dictType, value);
if (dictData == null) { if (dictData == null) {
return; return;
} }
// 如果 id 为空,说明不用比较是否为相同 id 的字典数据 // 如果 id 为空,说明不用比较是否为相同 id 的字典数据
if (id == null) { if (id == null) {
throw ServiceExceptionUtil.exception(DICT_DATA_VALUE_DUPLICATE); throw exception(DICT_DATA_VALUE_DUPLICATE);
} }
if (!dictData.getId().equals(id)) { if (!dictData.getId().equals(id)) {
throw ServiceExceptionUtil.exception(DICT_DATA_VALUE_DUPLICATE); throw exception(DICT_DATA_VALUE_DUPLICATE);
} }
} }
private void checkDictDataExists(Long id) { @VisibleForTesting
public void checkDictDataExists(Long id) {
if (id == null) { if (id == null) {
return; return;
} }
SysDictDataDO dictData = dictDataMapper.selectById(id); SysDictDataDO dictData = dictDataMapper.selectById(id);
if (dictData == null) { if (dictData == null) {
throw ServiceExceptionUtil.exception(DICT_DATA_NOT_EXISTS); throw exception(DICT_DATA_NOT_EXISTS);
} }
} }
private void checkDictTypeValid(String type) { @VisibleForTesting
public void checkDictTypeValid(String type) {
SysDictTypeDO dictType = dictTypeService.getDictType(type); SysDictTypeDO dictType = dictTypeService.getDictType(type);
if (dictType == null) { if (dictType == null) {
throw ServiceExceptionUtil.exception(DICT_TYPE_NOT_EXISTS); throw exception(DICT_TYPE_NOT_EXISTS);
} }
if (!CommonStatusEnum.ENABLE.getStatus().equals(dictType.getStatus())) { if (!CommonStatusEnum.ENABLE.getStatus().equals(dictType.getStatus())) {
throw ServiceExceptionUtil.exception(DICT_TYPE_NOT_ENABLE); throw exception(DICT_TYPE_NOT_ENABLE);
} }
} }

View File

@ -10,6 +10,7 @@ import cn.iocoder.dashboard.modules.system.dal.dataobject.dict.SysDictTypeDO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dict.SysDictTypeMapper; import cn.iocoder.dashboard.modules.system.dal.mysql.dict.SysDictTypeMapper;
import cn.iocoder.dashboard.modules.system.service.dict.SysDictDataService; import cn.iocoder.dashboard.modules.system.service.dict.SysDictDataService;
import cn.iocoder.dashboard.modules.system.service.dict.SysDictTypeService; import cn.iocoder.dashboard.modules.system.service.dict.SysDictTypeService;
import com.google.common.annotations.VisibleForTesting;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
@ -97,8 +98,9 @@ public class SysDictTypeServiceImpl implements SysDictTypeService {
checkDictTypeUnique(id, type); checkDictTypeUnique(id, type);
} }
private void checkDictTypeNameUnique(Long id, String type) { @VisibleForTesting
SysDictTypeDO dictType = dictTypeMapper.selectByName(type); public void checkDictTypeNameUnique(Long id, String name) {
SysDictTypeDO dictType = dictTypeMapper.selectByName(name);
if (dictType == null) { if (dictType == null) {
return; return;
} }
@ -111,7 +113,8 @@ public class SysDictTypeServiceImpl implements SysDictTypeService {
} }
} }
private void checkDictTypeUnique(Long id, String type) { @VisibleForTesting
public void checkDictTypeUnique(Long id, String type) {
SysDictTypeDO dictType = dictTypeMapper.selectByType(type); SysDictTypeDO dictType = dictTypeMapper.selectByType(type);
if (dictType == null) { if (dictType == null) {
return; return;
@ -125,7 +128,8 @@ public class SysDictTypeServiceImpl implements SysDictTypeService {
} }
} }
private SysDictTypeDO checkDictTypeExists(Long id) { @VisibleForTesting
public SysDictTypeDO checkDictTypeExists(Long id) {
if (id == null) { if (id == null) {
return null; return null;
} }

View File

@ -2,6 +2,8 @@ package cn.iocoder.dashboard.util.collection;
import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ArrayUtil;
import java.util.function.Consumer;
/** /**
* Array * Array
* *
@ -18,11 +20,11 @@ public class ArrayUtils {
* @return * @return
*/ */
@SafeVarargs @SafeVarargs
public static <T> T[] append(T object, T... newElements) { public static <T> Consumer<T>[] append(Consumer<T> object, Consumer<T>... newElements) {
if (object == null) { if (object == null) {
return newElements; return newElements;
} }
T[] result = ArrayUtil.newArray(object.getClass(), 1 + newElements.length); Consumer<T>[] result = ArrayUtil.newArray(Consumer.class, 1 + newElements.length);
result[0] = object; result[0] = object;
System.arraycopy(newElements, 0, result, 1, newElements.length); System.arraycopy(newElements, 0, result, 1, newElements.length);
return result; return result;

View File

@ -19,4 +19,14 @@ public class ObjectUtils {
return result; return result;
} }
public static <T extends Comparable<T>> T max(T obj1, T obj2) {
if (obj1 == null) {
return obj2;
}
if (obj2 == null) {
return obj1;
}
return obj1.compareTo(obj2) > 0 ? obj1 : obj2;
}
} }

View File

@ -0,0 +1,46 @@
package cn.iocoder.dashboard;
import cn.iocoder.dashboard.config.RedisTestConfiguration;
import cn.iocoder.dashboard.framework.datasource.config.DataSourceConfiguration;
import cn.iocoder.dashboard.framework.mybatis.config.MybatisConfiguration;
import cn.iocoder.dashboard.framework.redis.config.RedisConfig;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
import org.redisson.spring.starter.RedissonAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;
/**
* DB
*
* Service Service Mapper H2 Service Mock
*
* @author
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbAndRedisUnitTest.Application.class)
@ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件
@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后,清理 DB
public class BaseDbAndRedisUnitTest {
@Import({
// DB 配置类
DataSourceConfiguration.class, // 自己的 DB 配置类
DataSourceAutoConfiguration.class, // Spring DB 自动配置类
DruidDataSourceAutoConfigure.class, // Druid 自动配置类
// MyBatis 配置类
MybatisConfiguration.class, // 自己的 MyBatis 配置类
MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类
// Redis 配置类
RedisTestConfiguration.class, // Redis 测试配置类,用于启动 RedisServer
RedisAutoConfiguration.class, // Spring Redis 自动配置类
RedisConfig.class, // 自己的 Redis 配置类
RedissonAutoConfiguration.class, // Redisson 自动高配置类
})
public static class Application {
}
}

View File

@ -0,0 +1,37 @@
package cn.iocoder.dashboard;
import cn.iocoder.dashboard.framework.datasource.config.DataSourceConfiguration;
import cn.iocoder.dashboard.framework.mybatis.config.MybatisConfiguration;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;
/**
* DB
*
* Service Service Mapper H2 Service Mock
*
* @author
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbUnitTest.Application.class)
@ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件
@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后,清理 DB
public class BaseDbUnitTest {
@Import({
// DB 配置类
DataSourceConfiguration.class, // 自己的 DB 配置类
DataSourceAutoConfiguration.class, // Spring DB 自动配置类
DruidDataSourceAutoConfigure.class, // Druid 自动配置类
// MyBatis 配置类
MybatisConfiguration.class, // 自己的 MyBatis 配置类
MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类
})
public static class Application {
}
}

View File

@ -12,6 +12,7 @@ import javax.annotation.Resource;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
@ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件 @ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件
@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后,清理 DB @Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后,清理 DB
@Deprecated
public class BaseSpringBootUnitTest { public class BaseSpringBootUnitTest {
@Resource @Resource

View File

@ -8,12 +8,10 @@ import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import java.io.IOException; import java.io.IOException;
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@Lazy(false) // 禁用懒加载,因为需要保证 Redis Server 必须先启动
@EnableConfigurationProperties(RedisProperties.class) @EnableConfigurationProperties(RedisProperties.class)
@AutoConfigureBefore({RedisAutoConfiguration.class, RedissonAutoConfiguration.class}) // 在 Redis 自动配置前,进行初始化 @AutoConfigureBefore({RedisAutoConfiguration.class, RedissonAutoConfiguration.class}) // 在 Redis 自动配置前,进行初始化
public class RedisTestConfiguration { public class RedisTestConfiguration {

View File

@ -1,16 +0,0 @@
package cn.iocoder.dashboard.config;
import org.mockito.Mockito;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
@Configuration
public class SecurityTestConfiguration {
@Bean
public AuthenticationManager authenticationManager() {
return Mockito.mock(AuthenticationManager.class);
}
}

View File

@ -1,6 +1,6 @@
package cn.iocoder.dashboard.modules.infra.service.config; package cn.iocoder.dashboard.modules.infra.service.config;
import cn.iocoder.dashboard.BaseSpringBootUnitTest; import cn.iocoder.dashboard.BaseDbUnitTest;
import cn.iocoder.dashboard.common.pojo.PageResult; import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.modules.infra.controller.config.vo.InfConfigCreateReqVO; import cn.iocoder.dashboard.modules.infra.controller.config.vo.InfConfigCreateReqVO;
import cn.iocoder.dashboard.modules.infra.controller.config.vo.InfConfigExportReqVO; import cn.iocoder.dashboard.modules.infra.controller.config.vo.InfConfigExportReqVO;
@ -15,6 +15,7 @@ import cn.iocoder.dashboard.util.collection.ArrayUtils;
import cn.iocoder.dashboard.util.object.ObjectUtils; import cn.iocoder.dashboard.util.object.ObjectUtils;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.List; import java.util.List;
@ -24,8 +25,7 @@ import static cn.hutool.core.util.RandomUtil.randomEle;
import static cn.iocoder.dashboard.modules.infra.enums.InfErrorCodeConstants.*; import static cn.iocoder.dashboard.modules.infra.enums.InfErrorCodeConstants.*;
import static cn.iocoder.dashboard.util.AssertUtils.assertPojoEquals; import static cn.iocoder.dashboard.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.dashboard.util.AssertUtils.assertServiceException; import static cn.iocoder.dashboard.util.AssertUtils.assertServiceException;
import static cn.iocoder.dashboard.util.RandomUtils.randomLongId; import static cn.iocoder.dashboard.util.RandomUtils.*;
import static cn.iocoder.dashboard.util.RandomUtils.randomPojo;
import static cn.iocoder.dashboard.util.date.DateUtils.buildTime; import static cn.iocoder.dashboard.util.date.DateUtils.buildTime;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
@ -36,7 +36,8 @@ import static org.mockito.Mockito.verify;
* *
* @author * @author
*/ */
public class InfConfigServiceTest extends BaseSpringBootUnitTest { @Import(InfConfigServiceImpl.class)
public class InfConfigServiceTest extends BaseDbUnitTest {
@Resource @Resource
private InfConfigServiceImpl configService; private InfConfigServiceImpl configService;
@ -145,19 +146,6 @@ public class InfConfigServiceTest extends BaseSpringBootUnitTest {
verify(configProducer, times(1)).sendConfigRefreshMessage(); verify(configProducer, times(1)).sendConfigRefreshMessage();
} }
@Test
public void testCreateConfig_keyDuplicate() {
// 准备参数
InfConfigCreateReqVO reqVO = randomPojo(InfConfigCreateReqVO.class);
// mock 数据
configMapper.insert(randomInfConfigDO(o -> { // @Sql
o.setKey(reqVO.getKey()); // 模拟 key 重复
}));
// 调用, 并断言异常
assertServiceException(() -> configService.createConfig(reqVO), CONFIG_KEY_DUPLICATE);
}
@Test @Test
public void testUpdateConfig_success() { public void testUpdateConfig_success() {
// mock 数据 // mock 数据
@ -177,15 +165,6 @@ public class InfConfigServiceTest extends BaseSpringBootUnitTest {
verify(configProducer, times(1)).sendConfigRefreshMessage(); verify(configProducer, times(1)).sendConfigRefreshMessage();
} }
@Test
public void testUpdateConfig_notExists() {
// 准备参数
InfConfigUpdateReqVO reqVO = randomPojo(InfConfigUpdateReqVO.class);
// 调用, 并断言异常
assertServiceException(() -> configService.updateConfig(reqVO), CONFIG_NOT_EXISTS);
}
@Test @Test
public void testDeleteConfig_success() { public void testDeleteConfig_success() {
// mock 数据 // mock 数据
@ -219,12 +198,49 @@ public class InfConfigServiceTest extends BaseSpringBootUnitTest {
} }
@Test @Test
public void testDeleteConfig_notExists() { public void testCheckConfigExists_success() {
// mock 数据
InfConfigDO dbConfigDO = randomInfConfigDO();
configMapper.insert(dbConfigDO);// @Sql: 先插入出一条存在的数据
// 调用成功
configService.checkConfigExists(dbConfigDO.getId());
}
@Test
public void testCheckConfigExist_notExists() {
assertServiceException(() -> configService.checkConfigExists(randomLongId()), CONFIG_NOT_EXISTS);
}
@Test
public void testCheckConfigKeyUnique_success() {
// 调用,成功
configService.checkConfigKeyUnique(randomLongId(), randomString());
}
@Test
public void testCheckConfigKeyUnique_keyDuplicateForCreate() {
// 准备参数
String key = randomString();
// mock 数据
configMapper.insert(randomInfConfigDO(o -> o.setKey(key)));
// 调用,校验异常
assertServiceException(() -> configService.checkConfigKeyUnique(null, key),
CONFIG_KEY_DUPLICATE);
}
@Test
public void testCheckConfigKeyUnique_keyDuplicateForUpdate() {
// 准备参数 // 准备参数
Long id = randomLongId(); Long id = randomLongId();
String key = randomString();
// mock 数据
configMapper.insert(randomInfConfigDO(o -> o.setKey(key)));
// 调用, 并断言异常 // 调用,校验异常
assertServiceException(() -> configService.deleteConfig(id), CONFIG_NOT_EXISTS); assertServiceException(() -> configService.checkConfigKeyUnique(id, key),
CONFIG_KEY_DUPLICATE);
} }
// ========== 随机对象 ========== // ========== 随机对象 ==========

View File

@ -1,15 +1,19 @@
package cn.iocoder.dashboard.modules.system.service.auth; package cn.iocoder.dashboard.modules.system.service.auth;
import cn.iocoder.dashboard.BaseSpringBootUnitTest; import cn.iocoder.dashboard.BaseDbUnitTest;
import cn.iocoder.dashboard.common.enums.CommonStatusEnum; import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
import cn.iocoder.dashboard.framework.security.core.LoginUser; import cn.iocoder.dashboard.framework.security.core.LoginUser;
import cn.iocoder.dashboard.modules.system.dal.dataobject.user.SysUserDO; import cn.iocoder.dashboard.modules.system.dal.dataobject.user.SysUserDO;
import cn.iocoder.dashboard.modules.system.service.auth.impl.SysAuthServiceImpl; import cn.iocoder.dashboard.modules.system.service.auth.impl.SysAuthServiceImpl;
import cn.iocoder.dashboard.modules.system.service.common.SysCaptchaService;
import cn.iocoder.dashboard.modules.system.service.logger.SysLoginLogService;
import cn.iocoder.dashboard.modules.system.service.permission.SysPermissionService; import cn.iocoder.dashboard.modules.system.service.permission.SysPermissionService;
import cn.iocoder.dashboard.modules.system.service.user.SysUserService; import cn.iocoder.dashboard.modules.system.service.user.SysUserService;
import cn.iocoder.dashboard.util.AssertUtils; import cn.iocoder.dashboard.util.AssertUtils;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import javax.annotation.Resource; import javax.annotation.Resource;
@ -21,7 +25,13 @@ import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
public class SysAuthServiceImplTest extends BaseSpringBootUnitTest { /**
* {@link SysAuthServiceImpl}
*
* @author
*/
@Import(SysAuthServiceImpl.class)
public class SysAuthServiceImplTest extends BaseDbUnitTest {
@Resource @Resource
private SysAuthServiceImpl authService; private SysAuthServiceImpl authService;
@ -30,6 +40,14 @@ public class SysAuthServiceImplTest extends BaseSpringBootUnitTest {
private SysUserService userService; private SysUserService userService;
@MockBean @MockBean
private SysPermissionService permissionService; private SysPermissionService permissionService;
@MockBean
private AuthenticationManager authenticationManager;
@MockBean
private SysCaptchaService captchaService;
@MockBean
private SysLoginLogService loginLogService;
@MockBean
private SysUserSessionService userSessionService;
@Test @Test
public void testLoadUserByUsername_success() { public void testLoadUserByUsername_success() {

View File

@ -1,6 +1,6 @@
package cn.iocoder.dashboard.modules.system.service.dict; package cn.iocoder.dashboard.modules.system.service.dict;
import cn.iocoder.dashboard.BaseSpringBootUnitTest; import cn.iocoder.dashboard.BaseDbUnitTest;
import cn.iocoder.dashboard.common.enums.CommonStatusEnum; import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
import cn.iocoder.dashboard.common.pojo.PageResult; import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.modules.system.controller.dict.vo.data.SysDictDataCreateReqVO; import cn.iocoder.dashboard.modules.system.controller.dict.vo.data.SysDictDataCreateReqVO;
@ -12,14 +12,20 @@ import cn.iocoder.dashboard.modules.system.dal.dataobject.dict.SysDictTypeDO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dict.SysDictDataMapper; import cn.iocoder.dashboard.modules.system.dal.mysql.dict.SysDictDataMapper;
import cn.iocoder.dashboard.modules.system.mq.producer.dict.SysDictDataProducer; import cn.iocoder.dashboard.modules.system.mq.producer.dict.SysDictDataProducer;
import cn.iocoder.dashboard.modules.system.service.dict.impl.SysDictDataServiceImpl; import cn.iocoder.dashboard.modules.system.service.dict.impl.SysDictDataServiceImpl;
import cn.iocoder.dashboard.util.collection.ArrayUtils;
import cn.iocoder.dashboard.util.object.ObjectUtils; import cn.iocoder.dashboard.util.object.ObjectUtils;
import com.google.common.collect.ImmutableTable;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.function.Consumer;
import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.DICT_DATA_NOT_EXISTS; import static cn.hutool.core.bean.BeanUtil.getFieldValue;
import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
import static cn.iocoder.dashboard.util.AssertUtils.assertPojoEquals; import static cn.iocoder.dashboard.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.dashboard.util.AssertUtils.assertServiceException; import static cn.iocoder.dashboard.util.AssertUtils.assertServiceException;
import static cn.iocoder.dashboard.util.RandomUtils.*; import static cn.iocoder.dashboard.util.RandomUtils.*;
@ -32,7 +38,8 @@ import static org.mockito.Mockito.*;
* *
* @author * @author
*/ */
public class SysDictDataServiceTest extends BaseSpringBootUnitTest { @Import(SysDictDataServiceImpl.class)
public class SysDictDataServiceTest extends BaseDbUnitTest {
@Resource @Resource
private SysDictDataServiceImpl dictDataService; private SysDictDataServiceImpl dictDataService;
@ -44,6 +51,37 @@ public class SysDictDataServiceTest extends BaseSpringBootUnitTest {
@MockBean @MockBean
private SysDictDataProducer dictDataProducer; private SysDictDataProducer dictDataProducer;
/**
*
*/
@Test
@SuppressWarnings("unchecked")
public void testInitLocalCache() {
// mock 数据
SysDictDataDO dictData01 = randomDictDataDO();
dictDataMapper.insert(dictData01);
SysDictDataDO dictData02 = randomDictDataDO();
dictDataMapper.insert(dictData02);
// 调用
dictDataService.initLocalCache();
// 断言 labelDictDataCache 缓存
ImmutableTable<String, String, SysDictDataDO> labelDictDataCache =
(ImmutableTable<String, String, SysDictDataDO>) getFieldValue(dictDataService, "labelDictDataCache");
assertEquals(2, labelDictDataCache.size());
assertPojoEquals(dictData01, labelDictDataCache.get(dictData01.getDictType(), dictData01.getLabel()));
assertPojoEquals(dictData02, labelDictDataCache.get(dictData02.getDictType(), dictData02.getLabel()));
// 断言 valueDictDataCache 缓存
ImmutableTable<String, String, SysDictDataDO> valueDictDataCache =
(ImmutableTable<String, String, SysDictDataDO>) getFieldValue(dictDataService, "valueDictDataCache");
assertEquals(2, valueDictDataCache.size());
assertPojoEquals(dictData01, valueDictDataCache.get(dictData01.getDictType(), dictData01.getValue()));
assertPojoEquals(dictData02, valueDictDataCache.get(dictData02.getDictType(), dictData02.getValue()));
// 断言 maxUpdateTime 缓存
Date maxUpdateTime = (Date) getFieldValue(dictDataService, "maxUpdateTime");
assertEquals(ObjectUtils.max(dictData01.getUpdateTime(), dictData02.getUpdateTime()), maxUpdateTime);
}
@Test @Test
public void testGetDictDataPage() { public void testGetDictDataPage() {
// mock 数据 // mock 数据
@ -107,8 +145,7 @@ public class SysDictDataServiceTest extends BaseSpringBootUnitTest {
SysDictDataCreateReqVO reqVO = randomPojo(SysDictDataCreateReqVO.class, SysDictDataCreateReqVO reqVO = randomPojo(SysDictDataCreateReqVO.class,
o -> o.setStatus(randomCommonStatus())); o -> o.setStatus(randomCommonStatus()));
// mock 方法 // mock 方法
when(dictTypeService.getDictType(eq(reqVO.getDictType()))) when(dictTypeService.getDictType(eq(reqVO.getDictType()))).thenReturn(randomDictTypeDO(reqVO.getDictType()));
.thenReturn(randomPojo(SysDictTypeDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())));
// 调用 // 调用
Long dictDataId = dictDataService.createDictData(reqVO); Long dictDataId = dictDataService.createDictData(reqVO);
@ -124,50 +161,142 @@ public class SysDictDataServiceTest extends BaseSpringBootUnitTest {
@Test @Test
public void testUpdateDictData_success() { public void testUpdateDictData_success() {
// mock 数据 // mock 数据
SysDictDataDO dbDictData = randomPojo(SysDictDataDO.class); SysDictDataDO dbDictData = randomDictDataDO();
dictDataMapper.insert(dbDictData);// @Sql: 先插入出一条存在的数据 dictDataMapper.insert(dbDictData);// @Sql: 先插入出一条存在的数据
// 准备参数 // 准备参数
SysDictDataUpdateReqVO reqVO = randomPojo(SysDictDataUpdateReqVO.class, o -> { SysDictDataUpdateReqVO reqVO = randomPojo(SysDictDataUpdateReqVO.class, o -> {
o.setId(dbDictData.getId()); // 设置更新的 ID o.setId(dbDictData.getId()); // 设置更新的 ID
o.setStatus(randomCommonStatus());
}); });
// mock 方法,字典类型
when(dictTypeService.getDictType(eq(reqVO.getDictType()))).thenReturn(randomDictTypeDO(reqVO.getDictType()));
// 调用 // 调用
dictDataService.updateDictData(reqVO); dictDataService.updateDictData(reqVO);
// 校验是否更新正确 // 校验是否更新正确
SysDictDataDO dictData = dictDataMapper.selectById(reqVO.getId()); // 获取最新的 SysDictDataDO dictData = dictDataMapper.selectById(reqVO.getId()); // 获取最新的
assertPojoEquals(reqVO, dictData); assertPojoEquals(reqVO, dictData);
} // 校验调用
verify(dictDataProducer, times(1)).sendDictDataRefreshMessage();
@Test
public void testUpdateDictData_notExists() {
// 准备参数
SysDictDataUpdateReqVO reqVO = randomPojo(SysDictDataUpdateReqVO.class);
// 调用, 并断言异常
assertServiceException(() -> dictDataService.updateDictData(reqVO), DICT_DATA_NOT_EXISTS);
} }
@Test @Test
public void testDeleteDictData_success() { public void testDeleteDictData_success() {
// mock 数据 // mock 数据
SysDictDataDO dbDictData = randomPojo(SysDictDataDO.class); SysDictDataDO dbDictData = randomDictDataDO();
dictDataMapper.insert(dbDictData);// @Sql: 先插入出一条存在的数据 dictDataMapper.insert(dbDictData);// @Sql: 先插入出一条存在的数据
// 准备参数 // 准备参数
Long id = dbDictData.getId(); Long id = dbDictData.getId();
// 调用 // 调用
dictDataService.deleteDictData(id); dictDataService.deleteDictData(id);
// 校验数据不存在了 // 校验数据不存在了
assertNull(dictDataMapper.selectById(id)); assertNull(dictDataMapper.selectById(id));
// 校验调用
verify(dictDataProducer, times(1)).sendDictDataRefreshMessage();
} }
@Test @Test
public void testDeleteDictData_notExists() { public void testCheckDictDataExists_success() {
// 准备参数 // mock 数据
Long id = randomLongId(); SysDictDataDO dbDictData = randomDictDataDO();
dictDataMapper.insert(dbDictData);// @Sql: 先插入出一条存在的数据
// 调用成功
dictDataService.checkDictDataExists(dbDictData.getId());
}
@Test
public void testCheckDictDataExists_notExists() {
assertServiceException(() -> dictDataService.checkDictDataExists(randomLongId()), DICT_DATA_NOT_EXISTS);
}
@Test
public void testCheckDictTypeValid_success() {
// mock 方法,数据类型被禁用
String type = randomString();
when(dictTypeService.getDictType(eq(type))).thenReturn(randomDictTypeDO(type));
// 调用, 成功
dictDataService.checkDictTypeValid(type);
}
@Test
public void testCheckDictTypeValid_notExists() {
assertServiceException(() -> dictDataService.checkDictTypeValid(randomString()), DICT_TYPE_NOT_EXISTS);
}
@Test
public void testCheckDictTypeValid_notEnable() {
// mock 方法,数据类型被禁用
String dictType = randomString();
when(dictTypeService.getDictType(eq(dictType))).thenReturn(
randomPojo(SysDictTypeDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
// 调用, 并断言异常 // 调用, 并断言异常
assertServiceException(() -> dictDataService.deleteDictData(id), DICT_DATA_NOT_EXISTS); assertServiceException(() -> dictDataService.checkDictTypeValid(dictType), DICT_TYPE_NOT_ENABLE);
}
@Test
public void testCheckDictDataValueUnique_success() {
// 调用,成功
dictDataService.checkDictDataValueUnique(randomLongId(), randomString(), randomString());
}
@Test
public void testCheckDictDataValueUnique_valueDuplicateForCreate() {
// 准备参数
String dictType = randomString();
String value = randomString();
// mock 数据
dictDataMapper.insert(randomDictDataDO(o -> {
o.setDictType(dictType);
o.setValue(value);
}));
// 调用,校验异常
assertServiceException(() -> dictDataService.checkDictDataValueUnique(null, dictType, value),
DICT_DATA_VALUE_DUPLICATE);
}
@Test
public void testCheckDictDataValueUnique_valueDuplicateForUpdate() {
// 准备参数
Long id = randomLongId();
String dictType = randomString();
String value = randomString();
// mock 数据
dictDataMapper.insert(randomDictDataDO(o -> {
o.setDictType(dictType);
o.setValue(value);
}));
// 调用,校验异常
assertServiceException(() -> dictDataService.checkDictDataValueUnique(id, dictType, value),
DICT_DATA_VALUE_DUPLICATE);
}
// ========== 随机对象 ==========
@SafeVarargs
private static SysDictDataDO randomDictDataDO(Consumer<SysDictDataDO>... consumers) {
Consumer<SysDictDataDO> consumer = (o) -> {
o.setStatus(randomCommonStatus()); // 保证 status 的范围
};
return randomPojo(SysDictDataDO.class, ArrayUtils.append(consumer, consumers));
}
/**
*
*
* @param type
* @return SysDictTypeDO
*/
private static SysDictTypeDO randomDictTypeDO(String type) {
return randomPojo(SysDictTypeDO.class, o -> {
o.setType(type);
o.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 保证 status 是开启
});
} }
} }

View File

@ -1,6 +1,6 @@
package cn.iocoder.dashboard.modules.system.service.dict; package cn.iocoder.dashboard.modules.system.service.dict;
import cn.iocoder.dashboard.BaseSpringBootUnitTest; import cn.iocoder.dashboard.BaseDbUnitTest;
import cn.iocoder.dashboard.common.enums.CommonStatusEnum; import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
import cn.iocoder.dashboard.common.pojo.PageResult; import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.modules.system.controller.dict.vo.type.SysDictTypeCreateReqVO; import cn.iocoder.dashboard.modules.system.controller.dict.vo.type.SysDictTypeCreateReqVO;
@ -14,6 +14,7 @@ import cn.iocoder.dashboard.util.collection.ArrayUtils;
import cn.iocoder.dashboard.util.object.ObjectUtils; import cn.iocoder.dashboard.util.object.ObjectUtils;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.List; import java.util.List;
@ -23,8 +24,7 @@ import static cn.hutool.core.util.RandomUtil.randomEle;
import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*; import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
import static cn.iocoder.dashboard.util.AssertUtils.assertPojoEquals; import static cn.iocoder.dashboard.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.dashboard.util.AssertUtils.assertServiceException; import static cn.iocoder.dashboard.util.AssertUtils.assertServiceException;
import static cn.iocoder.dashboard.util.RandomUtils.randomLongId; import static cn.iocoder.dashboard.util.RandomUtils.*;
import static cn.iocoder.dashboard.util.RandomUtils.randomPojo;
import static cn.iocoder.dashboard.util.date.DateUtils.buildTime; import static cn.iocoder.dashboard.util.date.DateUtils.buildTime;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
@ -35,7 +35,8 @@ import static org.mockito.Mockito.when;
* *
* @author * @author
*/ */
public class SysDictTypeServiceTest extends BaseSpringBootUnitTest { @Import(SysDictTypeServiceImpl.class)
public class SysDictTypeServiceTest extends BaseDbUnitTest {
@Resource @Resource
private SysDictTypeServiceImpl dictTypeService; private SysDictTypeServiceImpl dictTypeService;
@ -142,32 +143,6 @@ public class SysDictTypeServiceTest extends BaseSpringBootUnitTest {
assertPojoEquals(reqVO, dictType); assertPojoEquals(reqVO, dictType);
} }
@Test
public void testCreateDictType_nameDuplicate() {
// mock 数据
SysDictTypeDO dbDictType = randomDictTypeDO();
dictTypeMapper.insert(dbDictType);// @Sql: 先插入出一条存在的数据
// 准备参数
SysDictTypeCreateReqVO reqVO = randomPojo(SysDictTypeCreateReqVO.class,
o -> o.setName(dbDictType.getName())); // 模拟 name 重复
// 调用, 并断言异常
assertServiceException(() -> dictTypeService.createDictType(reqVO), DICT_TYPE_NAME_DUPLICATE);
}
@Test
public void testCreateDictType_typeDuplicate() {
// mock 数据
SysDictTypeDO dbDictType = randomDictTypeDO();
dictTypeMapper.insert(dbDictType);// @Sql: 先插入出一条存在的数据
// 准备参数
SysDictTypeCreateReqVO reqVO = randomPojo(SysDictTypeCreateReqVO.class,
o -> o.setType(dbDictType.getType())); // 模拟 type 重复
// 调用, 并断言异常
assertServiceException(() -> dictTypeService.createDictType(reqVO), DICT_TYPE_TYPE_DUPLICATE);
}
@Test @Test
public void testUpdateDictType_success() { public void testUpdateDictType_success() {
// mock 数据 // mock 数据
@ -186,33 +161,6 @@ public class SysDictTypeServiceTest extends BaseSpringBootUnitTest {
assertPojoEquals(reqVO, dictType); assertPojoEquals(reqVO, dictType);
} }
@Test
public void testUpdateDictType_notExists() {
// 准备参数
SysDictTypeUpdateReqVO reqVO = randomPojo(SysDictTypeUpdateReqVO.class);
// 调用, 并断言异常
assertServiceException(() -> dictTypeService.updateDictType(reqVO), DICT_TYPE_NOT_EXISTS);
}
@Test
public void testUpdateDictType_nameDuplicate() {
// mock 数据,稍后更新它
SysDictTypeDO dbDictType = randomDictTypeDO();
dictTypeMapper.insert(dbDictType);
// mock 数据ks稍后模拟重复它的名字
SysDictTypeDO nameDictType = randomDictTypeDO();
dictTypeMapper.insert(nameDictType);
// 准备参数
SysDictTypeUpdateReqVO reqVO = randomPojo(SysDictTypeUpdateReqVO.class, o -> {
o.setId(dbDictType.getId()); // 设置更新的 ID
o.setName(nameDictType.getName()); // 模拟 name 重复
});
// 调用, 并断言异常
assertServiceException(() -> dictTypeService.updateDictType(reqVO), DICT_TYPE_NAME_DUPLICATE);
}
@Test @Test
public void testDeleteDictType_success() { public void testDeleteDictType_success() {
// mock 数据 // mock 数据
@ -227,15 +175,6 @@ public class SysDictTypeServiceTest extends BaseSpringBootUnitTest {
assertNull(dictTypeMapper.selectById(id)); assertNull(dictTypeMapper.selectById(id));
} }
@Test
public void testDeleteDictType_notExists() {
// 准备参数
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> dictTypeService.deleteDictType(id), DICT_TYPE_NOT_EXISTS);
}
@Test @Test
public void testDeleteDictType_hasChildren() { public void testDeleteDictType_hasChildren() {
// mock 数据 // mock 数据
@ -250,6 +189,83 @@ public class SysDictTypeServiceTest extends BaseSpringBootUnitTest {
assertServiceException(() -> dictTypeService.deleteDictType(id), DICT_TYPE_HAS_CHILDREN); assertServiceException(() -> dictTypeService.deleteDictType(id), DICT_TYPE_HAS_CHILDREN);
} }
@Test
public void testCheckDictDataExists_success() {
// mock 数据
SysDictTypeDO dbDictType = randomDictTypeDO();
dictTypeMapper.insert(dbDictType);// @Sql: 先插入出一条存在的数据
// 调用成功
dictTypeService.checkDictTypeExists(dbDictType.getId());
}
@Test
public void testCheckDictDataExists_notExists() {
assertServiceException(() -> dictTypeService.checkDictTypeExists(randomLongId()), DICT_TYPE_NOT_EXISTS);
}
@Test
public void testCheckDictTypeUnique_success() {
// 调用,成功
dictTypeService.checkDictTypeUnique(randomLongId(), randomString());
}
@Test
public void testCheckDictTypeUnique_valueDuplicateForCreate() {
// 准备参数
String type = randomString();
// mock 数据
dictTypeMapper.insert(randomDictTypeDO(o -> o.setType(type)));
// 调用,校验异常
assertServiceException(() -> dictTypeService.checkDictTypeUnique(null, type),
DICT_TYPE_TYPE_DUPLICATE);
}
@Test
public void testCheckDictTypeUnique_valueDuplicateForUpdate() {
// 准备参数
Long id = randomLongId();
String type = randomString();
// mock 数据
dictTypeMapper.insert(randomDictTypeDO(o -> o.setType(type)));
// 调用,校验异常
assertServiceException(() -> dictTypeService.checkDictTypeUnique(id, type),
DICT_TYPE_TYPE_DUPLICATE);
}
@Test
public void testCheckDictTypNameUnique_success() {
// 调用,成功
dictTypeService.checkDictTypeNameUnique(randomLongId(), randomString());
}
@Test
public void testCheckDictTypeNameUnique_nameDuplicateForCreate() {
// 准备参数
String name = randomString();
// mock 数据
dictTypeMapper.insert(randomDictTypeDO(o -> o.setName(name)));
// 调用,校验异常
assertServiceException(() -> dictTypeService.checkDictTypeNameUnique(null, name),
DICT_TYPE_NAME_DUPLICATE);
}
@Test
public void testCheckDictTypeNameUnique_nameDuplicateForUpdate() {
// 准备参数
Long id = randomLongId();
String name = randomString();
// mock 数据
dictTypeMapper.insert(randomDictTypeDO(o -> o.setName(name)));
// 调用,校验异常
assertServiceException(() -> dictTypeService.checkDictTypeNameUnique(id, name),
DICT_TYPE_NAME_DUPLICATE);
}
// ========== 随机对象 ========== // ========== 随机对象 ==========
@SafeVarargs @SafeVarargs

View File

@ -3,21 +3,6 @@ spring:
lazy-initialization: true # 开启懒加载,加快速度 lazy-initialization: true # 开启懒加载,加快速度
banner-mode: off # 单元测试,禁用 Banner banner-mode: off # 单元测试,禁用 Banner
# 去除的自动配置项
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration # 单元测试,禁用 SpringSecurity
- org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration # 单元测试,禁用 SpringSecurity
- org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration # 单元测试,禁用 Quartz
- com.baomidou.lock.spring.boot.autoconfigure.LockAutoConfiguration # 单元测试,禁用 Lock4j 分布式锁
- org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration # 单元测试,禁用 Scheduler 定时任务
- org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration # 项目没有使用 Spring Data所以禁用配置类加速启动
# Swagger 接口文档的自动配置(单元测试,禁用 Swagger)
springfox:
documentation:
auto-startup: false
--- #################### 数据库相关配置 #################### --- #################### 数据库相关配置 ####################
spring: spring:
@ -29,6 +14,9 @@ spring:
username: sa username: sa
password: password:
schema: classpath:sql/create_tables.sql # MySQL 转 H2 的语句,使用 https://www.jooq.org/translate/ 工具 schema: classpath:sql/create_tables.sql # MySQL 转 H2 的语句,使用 https://www.jooq.org/translate/ 工具
druid:
async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度
initial-size: 1 # 单元测试,配置为 1提升启动速度
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
redis: redis:
@ -36,17 +24,13 @@ spring:
port: 16379 # 端口(单元测试,使用 16379 端口) port: 16379 # 端口(单元测试,使用 16379 端口)
database: 0 # 数据库索引 database: 0 # 数据库索引
mybatis:
lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试
--- #################### 定时任务相关配置 #################### --- #################### 定时任务相关配置 ####################
# Quartz 配置项,对应 QuartzProperties 配置类(单元测试,禁用 Quartz
--- #################### 配置中心相关配置 #################### --- #################### 配置中心相关配置 ####################
# Apollo 配置中心
apollo:
bootstrap:
enabled: false # 单元测试,禁用配置中心
--- #################### 服务保障相关配置 #################### --- #################### 服务保障相关配置 ####################
# Lock4j 配置项(单元测试,禁用 Lock4j # Lock4j 配置项(单元测试,禁用 Lock4j
@ -63,23 +47,6 @@ resilience4j:
--- #################### 监控相关配置 #################### --- #################### 监控相关配置 ####################
# Actuator 监控端点的配置项
management:
endpoints:
enabled-by-default: false
# Spring Boot Admin 配置项
spring:
boot:
admin:
# Spring Boot Admin Client 客户端的相关配置
client:
enabled: false
# Spring Boot Admin Server 服务端的相关配置
context-path: /admin # 配置 Spring
# 日志文件配置(不需要配置)
--- #################### 芋道相关配置 #################### --- #################### 芋道相关配置 ####################
# 芋道配置项,设置当前项目所有自定义的配置 # 芋道配置项,设置当前项目所有自定义的配置

View File

@ -9,9 +9,9 @@ CREATE TABLE IF NOT EXISTS "inf_config" (
"value" varchar(500) NOT NULL DEFAULT '', "value" varchar(500) NOT NULL DEFAULT '',
"sensitive" bit NOT NULL, "sensitive" bit NOT NULL,
"remark" varchar(500) DEFAULT NULL, "remark" varchar(500) DEFAULT NULL,
"create_by" varchar(64) DEFAULT '', "creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"update_by" varchar(64) DEFAULT '', "updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE, "deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id") PRIMARY KEY ("id")
@ -28,9 +28,9 @@ CREATE TABLE IF NOT EXISTS "sys_dept" (
"phone" varchar(11) DEFAULT NULL, "phone" varchar(11) DEFAULT NULL,
"email" varchar(50) DEFAULT NULL, "email" varchar(50) DEFAULT NULL,
"status" tinyint NOT NULL, "status" tinyint NOT NULL,
"create_by" varchar(64) DEFAULT '', "creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"update_by" varchar(64) DEFAULT '', "updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE, "deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id") PRIMARY KEY ("id")
@ -44,9 +44,9 @@ CREATE TABLE IF NOT EXISTS "sys_dict_data" (
"dict_type" varchar(100) NOT NULL DEFAULT '', "dict_type" varchar(100) NOT NULL DEFAULT '',
"status" tinyint NOT NULL DEFAULT '0', "status" tinyint NOT NULL DEFAULT '0',
"remark" varchar(500) DEFAULT NULL, "remark" varchar(500) DEFAULT NULL,
"create_by" varchar(64) DEFAULT '', "creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"update_by" varchar(64) DEFAULT '', "updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE, "deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id") PRIMARY KEY ("id")
@ -62,9 +62,9 @@ CREATE TABLE IF NOT EXISTS "sys_role" (
"status" tinyint NOT NULL, "status" tinyint NOT NULL,
"type" tinyint NOT NULL, "type" tinyint NOT NULL,
"remark" varchar(500) DEFAULT NULL, "remark" varchar(500) DEFAULT NULL,
"create_by" varchar(64) DEFAULT '', "creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"update_by" varchar(64) DEFAULT '', "updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE, "deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id") PRIMARY KEY ("id")
@ -74,9 +74,9 @@ CREATE TABLE IF NOT EXISTS "sys_role_menu" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"role_id" bigint NOT NULL, "role_id" bigint NOT NULL,
"menu_id" bigint NOT NULL, "menu_id" bigint NOT NULL,
"create_by" varchar(64) DEFAULT '', "creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"update_by" varchar(64) DEFAULT '', "updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE, "deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id") PRIMARY KEY ("id")
@ -93,9 +93,9 @@ CREATE TABLE IF NOT EXISTS "sys_menu" (
"icon" varchar(100) DEFAULT '#', "icon" varchar(100) DEFAULT '#',
"component" varchar(255) DEFAULT NULL, "component" varchar(255) DEFAULT NULL,
"status" tinyint NOT NULL DEFAULT '0', "status" tinyint NOT NULL DEFAULT '0',
"create_by" varchar(64) DEFAULT '', "creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"update_by" varchar(64) DEFAULT '', "updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE, "deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id") PRIMARY KEY ("id")
@ -107,9 +107,9 @@ CREATE TABLE "sys_dict_type" (
"type" varchar(100) NOT NULL DEFAULT '', "type" varchar(100) NOT NULL DEFAULT '',
"status" tinyint NOT NULL DEFAULT '0', "status" tinyint NOT NULL DEFAULT '0',
"remark" varchar(500) DEFAULT NULL, "remark" varchar(500) DEFAULT NULL,
"create_by" varchar(64) DEFAULT '', "creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"update_by" varchar(64) DEFAULT '', "updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE, "deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id") PRIMARY KEY ("id")