Browse Source

Merge branch 'rouyi/master' into feature/notice_test

# Conflicts:
#	src/test/resources/sql/create_tables.sql
budliang 4 years ago
parent
commit
a5090267ef
66 changed files with 1416 additions and 2986 deletions
  1. 6 3
      README.md
  2. 27 1
      pom.xml
  3. 27 25
      ruoyi-ui/src/views/system/dict/index.vue
  4. 5 2702
      sql/ruoyi-vue-pro.sql
  5. 3 1
      src/main/java/cn/iocoder/dashboard/common/exception/util/ServiceExceptionUtil.java
  6. 5 2
      src/main/java/cn/iocoder/dashboard/common/pojo/PageParam.java
  7. 4 4
      src/main/java/cn/iocoder/dashboard/framework/mybatis/core/dataobject/BaseDO.java
  8. 65 0
      src/main/java/cn/iocoder/dashboard/framework/mybatis/core/handle/DefaultDBFieldHandler.java
  9. 8 4
      src/main/java/cn/iocoder/dashboard/framework/mybatis/core/mapper/BaseMapperX.java
  10. 0 1
      src/main/java/cn/iocoder/dashboard/framework/resilience4j/《芋道 Spring Boot 安全框架 Spring Security 入门》.md
  11. 1 0
      src/main/java/cn/iocoder/dashboard/framework/resilience4j/《芋道 Spring Boot 服务容错 Resilience4j 入门》.md
  12. 6 4
      src/main/java/cn/iocoder/dashboard/framework/swagger/config/SwaggerAutoConfiguration.java
  13. 2 1
      src/main/java/cn/iocoder/dashboard/framework/web/config/WebConfiguration.java
  14. 1 1
      src/main/java/cn/iocoder/dashboard/modules/infra/enums/InfErrorCodeConstants.java
  15. 6 3
      src/main/java/cn/iocoder/dashboard/modules/infra/service/config/impl/InfConfigServiceImpl.java
  16. 1 1
      src/main/java/cn/iocoder/dashboard/modules/system/controller/dept/vo/dept/SysDeptBaseVO.java
  17. 1 1
      src/main/java/cn/iocoder/dashboard/modules/system/controller/dept/vo/post/SysPostBaseVO.java
  18. 1 1
      src/main/java/cn/iocoder/dashboard/modules/system/controller/dept/vo/post/SysPostExcelVO.java
  19. 3 3
      src/main/java/cn/iocoder/dashboard/modules/system/controller/dict/SysDictDataController.java
  20. 3 3
      src/main/java/cn/iocoder/dashboard/modules/system/controller/dict/SysDictTypeController.java
  21. 1 1
      src/main/java/cn/iocoder/dashboard/modules/system/controller/dict/vo/data/SysDictDataBaseVO.java
  22. 4 6
      src/main/java/cn/iocoder/dashboard/modules/system/controller/dict/vo/type/SysDictTypeExportReqVO.java
  23. 4 4
      src/main/java/cn/iocoder/dashboard/modules/system/controller/dict/vo/type/SysDictTypePageReqVO.java
  24. 1 1
      src/main/java/cn/iocoder/dashboard/modules/system/controller/permission/vo/menu/SysMenuBaseVO.java
  25. 1 1
      src/main/java/cn/iocoder/dashboard/modules/system/controller/permission/vo/role/SysRoleBaseVO.java
  26. 1 1
      src/main/java/cn/iocoder/dashboard/modules/system/dal/dataobject/dept/SysDeptDO.java
  27. 1 1
      src/main/java/cn/iocoder/dashboard/modules/system/dal/dataobject/dept/SysPostDO.java
  28. 1 1
      src/main/java/cn/iocoder/dashboard/modules/system/dal/dataobject/dict/SysDictTypeDO.java
  29. 1 1
      src/main/java/cn/iocoder/dashboard/modules/system/dal/dataobject/permission/SysMenuDO.java
  30. 3 3
      src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dict/SysDictDataMapper.java
  31. 8 7
      src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dict/SysDictTypeMapper.java
  32. 2 2
      src/main/java/cn/iocoder/dashboard/modules/system/enums/SysErrorCodeConstants.java
  33. 3 3
      src/main/java/cn/iocoder/dashboard/modules/system/service/dict/SysDictDataService.java
  34. 3 3
      src/main/java/cn/iocoder/dashboard/modules/system/service/dict/SysDictTypeService.java
  35. 23 19
      src/main/java/cn/iocoder/dashboard/modules/system/service/dict/impl/SysDictDataServiceImpl.java
  36. 19 15
      src/main/java/cn/iocoder/dashboard/modules/system/service/dict/impl/SysDictTypeServiceImpl.java
  37. 8 5
      src/main/java/cn/iocoder/dashboard/modules/tool/service/codegen/impl/ToolCodegenEngine.java
  38. 33 0
      src/main/java/cn/iocoder/dashboard/util/collection/ArrayUtils.java
  39. 37 0
      src/main/java/cn/iocoder/dashboard/util/date/DateUtils.java
  40. 32 0
      src/main/java/cn/iocoder/dashboard/util/object/ObjectUtils.java
  41. 1 1
      src/main/resources/application-dev.yaml
  42. 1 1
      src/main/resources/application-local.yaml
  43. 5 5
      src/main/resources/codegen/java/controller/controller.vm
  44. 1 1
      src/main/resources/codegen/java/controller/vo/baseVO.vm
  45. 1 1
      src/main/resources/codegen/java/controller/vo/createReqVO.vm
  46. 1 1
      src/main/resources/codegen/java/controller/vo/excelVO.vm
  47. 1 1
      src/main/resources/codegen/java/controller/vo/exportReqVO.vm
  48. 1 1
      src/main/resources/codegen/java/controller/vo/pageReqVO.vm
  49. 1 1
      src/main/resources/codegen/java/controller/vo/respVO.vm
  50. 1 1
      src/main/resources/codegen/java/controller/vo/updateReqVO.vm
  51. 3 3
      src/main/resources/codegen/java/convert/convert.vm
  52. 1 1
      src/main/resources/codegen/java/dal/do.vm
  53. 3 3
      src/main/resources/codegen/java/dal/mapper.vm
  54. 3 3
      src/main/resources/codegen/java/service/service.vm
  55. 7 7
      src/main/resources/codegen/java/service/serviceImpl.vm
  56. 161 0
      src/main/resources/codegen/java/test/serviceTest.vm
  57. 0 103
      src/test/java/cn/iocoder/dashboard/modules/infra/service/config/InfConfigServiceImplTest.java
  58. 254 0
      src/test/java/cn/iocoder/dashboard/modules/infra/service/config/InfConfigServiceTest.java
  59. 2 2
      src/test/java/cn/iocoder/dashboard/modules/system/service/auth/SysAuthServiceImplTest.java
  60. 300 0
      src/test/java/cn/iocoder/dashboard/modules/system/service/dict/SysDictDataServiceTest.java
  61. 278 0
      src/test/java/cn/iocoder/dashboard/modules/system/service/dict/SysDictTypeServiceTest.java
  62. 0 1
      src/test/java/cn/iocoder/dashboard/modules/system/service/package-info.java
  63. 13 4
      src/test/java/cn/iocoder/dashboard/util/AssertUtils.java
  64. 6 1
      src/test/java/cn/iocoder/dashboard/util/RandomUtils.java
  65. 1 0
      src/test/resources/sql/clean.sql
  66. 9 9
      src/test/resources/sql/create_tables.sql

+ 6 - 3
README.md

@@ -43,11 +43,12 @@
 1. 幂等组件:基于 Redis 实现幂等组件,解决重复请求问题
 1. 服务保障:基于 Resilience4j 实现服务的稳定性,包括限流、熔断等功能
 1. 日志服务:轻量级日志中心,查看远程服务器的日志
+1. 单元测试:基于 JUnit + Mockito 实现单元测试,保证功能的正确性、代码的质量等
 
 ### 研发工具
 
 1. 表单构建:拖动表单元素生成相应的 HTML 代码
-1. 代码生成:前后端代码的生成(Java、Vue、SQL),支持 CRUD 下载
+1. 代码生成:前后端代码的生成(Java、Vue、SQL、单元测试),支持 CRUD 下载
 1. 系统接口:基于 Swagger 自动生成相关的 RESTful API 接口文档
 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) |
 | [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) |
-| [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>
         <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>
 </table>
 

+ 27 - 1
pom.xml

@@ -2,7 +2,7 @@
 <project xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-	<modelVersion>4.0.0</modelVersion>
+    <modelVersion>4.0.0</modelVersion>
 
     <groupId>cn.iocoder</groupId>
     <artifactId>dashboard</artifactId>
@@ -25,6 +25,7 @@
         <spring.boot.version>2.4.2</spring.boot.version>
         <!-- Web 相关 -->
         <knife4j.version>3.0.2</knife4j.version>
+        <swagger-annotations.version>1.5.22</swagger-annotations.version>
         <!-- DB 相关 -->
         <mysql-connector-java.version>5.1.46</mysql-connector-java.version>
         <druid.version>1.2.4</druid.version>
@@ -100,8 +101,21 @@
                     <artifactId>mapstruct</artifactId>
                     <groupId>org.mapstruct</groupId> <!-- 避免冲突 -->
                 </exclusion>
+                <exclusion>
+                    <artifactId>guava</artifactId>
+                    <groupId>com.google.guava</groupId>
+                </exclusion>
+                <exclusion>
+                    <artifactId>swagger-annotations</artifactId>
+                    <groupId>io.swagger</groupId>
+                </exclusion>
             </exclusions>
         </dependency>
+        <dependency>
+            <groupId>io.swagger</groupId>
+            <artifactId>swagger-annotations</artifactId>
+            <version>${swagger-annotations.version}</version>
+        </dependency>
 
         <!-- DB 相关 -->
         <dependency>
@@ -144,6 +158,12 @@
             <groupId>com.baomidou</groupId>
             <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
             <version>${lock4j.version}</version>
+            <exclusions>
+                <exclusion>
+                    <artifactId>redisson-spring-boot-starter</artifactId>
+                    <groupId>org.redisson</groupId>
+                </exclusion>
+            </exclusions>
         </dependency>
 
         <dependency>
@@ -175,6 +195,12 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>
             <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <artifactId>asm</artifactId>
+                    <groupId>org.ow2.asm</groupId>
+                </exclusion>
+            </exclusions>
         </dependency>
 
         <dependency>

+ 27 - 25
ruoyi-ui/src/views/system/dict/index.vue

@@ -39,7 +39,7 @@
       </el-form-item>
       <el-form-item label="创建时间">
         <el-date-picker
-          v-model="dateRange"
+          v-model="dateRangeCreateTime"
           size="small"
           style="width: 240px"
           value-format="yyyy-MM-dd"
@@ -177,7 +177,7 @@ export default {
       // 状态数据字典
       statusOptions: [],
       // 日期范围
-      dateRange: [],
+      dateRangeCreateTime: [],
       // 查询参数
       queryParams: {
         pageNo: 1,
@@ -211,15 +211,15 @@ export default {
     /** 查询字典类型列表 */
     getList() {
       this.loading = true;
-      listType(this.addDateRange(this.queryParams, [
-        this.dateRange[0] ? this.dateRange[0] + ' 00:00:00' : undefined,
-        this.dateRange[1] ? this.dateRange[1] + ' 23:59:59' : undefined,
-      ])).then(response => {
-          this.typeList = response.data.list;
-          this.total = response.data.total;
-          this.loading = false;
-        }
-      );
+      // 处理查询参数
+      let params = {...this.queryParams};
+      this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
+      // 执行查询
+      listType(params).then(response => {
+        this.typeList = response.data.list;
+        this.total = response.data.total;
+        this.loading = false;
+      });
     },
     // 字典状态字典翻译
     statusFormat(row, column) {
@@ -248,7 +248,7 @@ export default {
     },
     /** 重置按钮操作 */
     resetQuery() {
-      this.dateRange = [];
+      this.dateRangeCreateTime = [];
       this.resetForm("queryForm");
       this.handleQuery();
     },
@@ -304,19 +304,21 @@ export default {
     },
     /** 导出按钮操作 */
     handleExport() {
-      const queryParams = this.addDateRange(this.queryParams, [
-        this.dateRange[0] ? this.dateRange[0] + ' 00:00:00' : undefined,
-        this.dateRange[1] ? this.dateRange[1] + ' 23:59:59' : undefined,
-      ]);
-      this.$confirm('是否确认导出所有类型数据项?', "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(function() {
-          return exportType(queryParams);
-        }).then(response => {
-          this.downloadExcel(response, '数据类型.xls');
-        })
+      // 处理查询参数
+      let params = {...this.queryParams};
+      params.pageNo = undefined;
+      params.pageSize = undefined;
+      this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
+      // 执行导出
+      this.$confirm('是否确认导出所有字典类型数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return exportType(params);
+      }).then(response => {
+        this.downloadExcel(response, '字典类型.xls');
+      })
     }
   }
 };

File diff suppressed because it is too large
+ 5 - 2702
sql/ruoyi-vue-pro.sql


+ 3 - 1
src/main/java/cn/iocoder/dashboard/common/exception/util/ServiceExceptionUtil.java

@@ -2,6 +2,7 @@ package cn.iocoder.dashboard.common.exception.util;
 
 import cn.iocoder.dashboard.common.exception.ErrorCode;
 import cn.iocoder.dashboard.common.exception.ServiceException;
+import com.google.common.annotations.VisibleForTesting;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -91,7 +92,8 @@ public class ServiceExceptionUtil {
      * @param params         参数
      * @return 格式化后的提示
      */
-    private static String doFormat(int code, String messagePattern, Object... params) {
+    @VisibleForTesting
+    public static String doFormat(int code, String messagePattern, Object... params) {
         StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);
         int i = 0;
         int j;

+ 5 - 2
src/main/java/cn/iocoder/dashboard/common/pojo/PageParam.java

@@ -13,14 +13,17 @@ import java.io.Serializable;
 @Data
 public class PageParam implements Serializable {
 
+    private static final Integer PAGE_NO = 1;
+    private static final Integer PAGE_SIZE = 10;
+
     @ApiModelProperty(value = "页码,从 1 开始", required = true,example = "1")
     @NotNull(message = "页码不能为空")
     @Min(value = 1, message = "页码最小值为 1")
-    private Integer pageNo;
+    private Integer pageNo = PAGE_NO;
 
     @ApiModelProperty(value = "每页条数,最大值为 100", required = true, example = "10")
     @NotNull(message = "每页条数不能为空")
     @Range(min = 1, max = 100, message = "条数范围为 [1, 100]")
-    private Integer pageSize;
+    private Integer pageSize = PAGE_SIZE;
 
 }

+ 4 - 4
src/main/java/cn/iocoder/dashboard/framework/mybatis/core/dataobject/BaseDO.java

@@ -21,13 +21,13 @@ public class BaseDO implements Serializable {
      */
     private Date updateTime;
     /**
-     * 创建者 TODO 芋艿:迁移成编号
+     * 创建者
      */
-    private String createBy;
+    private String creator;
     /**
-     * 更新者 TODO 芋艿:迁移成编号
+     * 更新者
      */
-    private String updateBy;
+    private String updater;
     /**
      * 是否删除
      */

+ 65 - 0
src/main/java/cn/iocoder/dashboard/framework/mybatis/core/handle/DefaultDBFieldHandler.java

@@ -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);
+        }
+    }
+}

+ 8 - 4
src/main/java/cn/iocoder/dashboard/framework/mybatis/core/mapper/BaseMapperX.java

@@ -24,12 +24,16 @@ public interface BaseMapperX<T> extends BaseMapper<T> {
         return new PageResult<>(mpPage.getRecords(), mpPage.getTotal());
     }
 
-    default List<T> selectList() {
-        return selectList(new QueryWrapper<>());
-    }
-
     default T selectOne(String field, Object 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() {
+        return selectList(new QueryWrapper<>());
+    }
+
 }

+ 0 - 1
src/main/java/cn/iocoder/dashboard/framework/resilience4j/《芋道 Spring Boot 安全框架 Spring Security 入门》.md

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

+ 1 - 0
src/main/java/cn/iocoder/dashboard/framework/resilience4j/《芋道 Spring Boot 服务容错 Resilience4j 入门》.md

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

+ 6 - 4
src/main/java/cn/iocoder/dashboard/framework/swagger/config/SwaggerAutoConfiguration.java

@@ -10,15 +10,17 @@ import org.springframework.context.annotation.Configuration;
 import org.springframework.http.HttpHeaders;
 import springfox.documentation.builders.ApiInfoBuilder;
 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.service.contexts.SecurityContext;
 import springfox.documentation.spring.web.plugins.Docket;
 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.List;
 

+ 2 - 1
src/main/java/cn/iocoder/dashboard/framework/web/config/WebConfiguration.java

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

+ 1 - 1
src/main/java/cn/iocoder/dashboard/modules/infra/enums/InfErrorCodeConstants.java

@@ -10,7 +10,7 @@ import cn.iocoder.dashboard.common.exception.ErrorCode;
 public interface InfErrorCodeConstants {
 
     // ========== 参数配置 1001000000 ==========
-    ErrorCode CONFIG_NOT_FOUND = new ErrorCode(1001000001, "参数配置不存在");
+    ErrorCode CONFIG_NOT_EXISTS = new ErrorCode(1001000001, "参数配置不存在");
     ErrorCode CONFIG_KEY_DUPLICATE = new ErrorCode(1001000002, "参数配置 key 重复");
     ErrorCode CONFIG_CAN_NOT_DELETE_SYSTEM_TYPE = new ErrorCode(1001000003, "不能删除类型为系统内置的参数配置");
     ErrorCode CONFIG_GET_VALUE_ERROR_IF_SENSITIVE = new ErrorCode(1001000004, "不允许获取敏感配置到前端");

+ 6 - 3
src/main/java/cn/iocoder/dashboard/modules/infra/service/config/impl/InfConfigServiceImpl.java

@@ -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.mq.producer.config.InfConfigProducer;
 import cn.iocoder.dashboard.modules.infra.service.config.InfConfigService;
+import com.google.common.annotations.VisibleForTesting;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
@@ -99,18 +100,20 @@ public class InfConfigServiceImpl implements InfConfigService {
         checkConfigKeyUnique(id, key);
     }
 
-    private InfConfigDO checkConfigExists(Long id) {
+    @VisibleForTesting
+    public InfConfigDO checkConfigExists(Long id) {
         if (id == null) {
             return null;
         }
         InfConfigDO config = configMapper.selectById(id);
         if (config == null) {
-            throw ServiceExceptionUtil.exception(CONFIG_NOT_FOUND);
+            throw ServiceExceptionUtil.exception(CONFIG_NOT_EXISTS);
         }
         return config;
     }
 
-    private void checkConfigKeyUnique(Long id, String key) {
+    @VisibleForTesting
+    public void checkConfigKeyUnique(Long id, String key) {
         InfConfigDO config = configMapper.selectByKey(key);
         if (config == null) {
             return;

+ 1 - 1
src/main/java/cn/iocoder/dashboard/modules/system/controller/dept/vo/dept/SysDeptBaseVO.java

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

+ 1 - 1
src/main/java/cn/iocoder/dashboard/modules/system/controller/dept/vo/post/SysPostBaseVO.java

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

+ 1 - 1
src/main/java/cn/iocoder/dashboard/modules/system/controller/dept/vo/post/SysPostExcelVO.java

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

+ 3 - 3
src/main/java/cn/iocoder/dashboard/modules/system/controller/dict/SysDictDataController.java

@@ -32,7 +32,7 @@ public class SysDictDataController {
     @GetMapping("/list-all-simple")
     // 无需添加权限认证,因为前端全局都需要
     public CommonResult<List<SysDictDataSimpleVO>> listSimpleDictDatas() {
-        List<SysDictDataDO> list = dictDataService.listDictDatas();
+        List<SysDictDataDO> list = dictDataService.getDictDataList();
         return success(SysDictDataConvert.INSTANCE.convertList(list));
     }
 
@@ -40,7 +40,7 @@ public class SysDictDataController {
     @GetMapping("/page")
 //    @PreAuthorize("@ss.hasPermi('system:dict:list')")
     public CommonResult<PageResult<SysDictDataRespVO>> pageDictTypes(@Validated SysDictDataPageReqVO reqVO) {
-        return success(SysDictDataConvert.INSTANCE.convertPage(dictDataService.pageDictDatas(reqVO)));
+        return success(SysDictDataConvert.INSTANCE.convertPage(dictDataService.getDictDataPage(reqVO)));
     }
 
     @ApiOperation("/查询字典数据详细")
@@ -83,7 +83,7 @@ public class SysDictDataController {
 //    @Log(title = "字典类型", businessType = BusinessType.EXPORT)
 //    @PreAuthorize("@ss.hasPermi('system:dict:export')")
     public void export(HttpServletResponse response, @Validated SysDictDataExportReqVO reqVO) throws IOException {
-        List<SysDictDataDO> list = dictDataService.listDictDatas(reqVO);
+        List<SysDictDataDO> list = dictDataService.getDictDataList(reqVO);
         List<SysDictDataExcelVO> excelDataList = SysDictDataConvert.INSTANCE.convertList02(list);
         // 输出
         ExcelUtils.write(response, "字典数据.xls", "数据列表",

+ 3 - 3
src/main/java/cn/iocoder/dashboard/modules/system/controller/dict/SysDictTypeController.java

@@ -32,7 +32,7 @@ public class SysDictTypeController {
     @GetMapping("/page")
 //    @PreAuthorize("@ss.hasPermi('system:dict:list')")
     public CommonResult<PageResult<SysDictTypeRespVO>> pageDictTypes(@Validated SysDictTypePageReqVO reqVO) {
-        return success(SysDictTypeConvert.INSTANCE.convertPage(dictTypeService.pageDictTypes(reqVO)));
+        return success(SysDictTypeConvert.INSTANCE.convertPage(dictTypeService.getDictTypePage(reqVO)));
     }
 
     @ApiOperation("/查询字典类型详细")
@@ -75,7 +75,7 @@ public class SysDictTypeController {
     @ApiOperation(value = "获得全部字典类型列表", notes = "包括开启 + 禁用的字典类型,主要用于前端的下拉选项")
     // 无需添加权限认证,因为前端全局都需要
     public CommonResult<List<SysDictTypeSimpleRespVO>> listSimpleDictTypes() {
-        List<SysDictTypeDO> list = dictTypeService.listDictTypes();
+        List<SysDictTypeDO> list = dictTypeService.getDictTypeList();
         return success(SysDictTypeConvert.INSTANCE.convertList(list));
     }
 
@@ -84,7 +84,7 @@ public class SysDictTypeController {
 //    @Log(title = "字典类型", businessType = BusinessType.EXPORT)
 //    @PreAuthorize("@ss.hasPermi('system:dict:export')")
     public void export(HttpServletResponse response, @Validated SysDictTypeExportReqVO reqVO) throws IOException {
-        List<SysDictTypeDO> list = dictTypeService.listDictTypes(reqVO);
+        List<SysDictTypeDO> list = dictTypeService.getDictTypeList(reqVO);
         List<SysDictTypeExcelVO> excelTypeList = SysDictTypeConvert.INSTANCE.convertList02(list);
         // 输出
         ExcelUtils.write(response, "字典类型.xls", "类型列表",

+ 1 - 1
src/main/java/cn/iocoder/dashboard/modules/system/controller/dict/vo/data/SysDictDataBaseVO.java

@@ -16,7 +16,7 @@ public class SysDictDataBaseVO {
 
     @ApiModelProperty(value = "显示顺序不能为空", required = true, example = "1024")
     @NotBlank(message = "显示顺序不能为空")
-    private String sort;
+    private Integer sort;
 
     @ApiModelProperty(value = "字典标签", required = true, example = "芋道")
     @NotBlank(message = "字典标签不能为空")

+ 4 - 6
src/main/java/cn/iocoder/dashboard/modules/system/controller/dict/vo/type/SysDictTypeExportReqVO.java

@@ -5,7 +5,6 @@ import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 import org.springframework.format.annotation.DateTimeFormat;
 
-import javax.validation.constraints.Size;
 import java.util.Date;
 
 import static cn.iocoder.dashboard.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@@ -18,18 +17,17 @@ public class SysDictTypeExportReqVO {
     private String name;
 
     @ApiModelProperty(value = "字典类型", example = "sys_common_sex", notes = "模糊匹配")
-    @Size(max = 100, message = "字典类型类型长度不能超过100个字符")
     private String type;
 
     @ApiModelProperty(value = "展示状态", example = "1", notes = "参见 SysCommonStatusEnum 枚举类")
     private Integer status;
 
-    @ApiModelProperty(value = "开始时间", example = "2020-10-24")
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private Date beginTime;
+    @ApiModelProperty(value = "开始创建时间")
+    private Date beginCreateTime;
 
-    @ApiModelProperty(value = "结束时间", example = "2020-10-24")
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private Date endTime;
+    @ApiModelProperty(value = "结束创建时间")
+    private Date endCreateTime;
 
 }

+ 4 - 4
src/main/java/cn/iocoder/dashboard/modules/system/controller/dict/vo/type/SysDictTypePageReqVO.java

@@ -27,12 +27,12 @@ public class SysDictTypePageReqVO extends PageParam {
     @ApiModelProperty(value = "展示状态", example = "1", notes = "参见 SysCommonStatusEnum 枚举类")
     private Integer status;
 
-    @ApiModelProperty(value = "开始时间", example = "2020-10-24")
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private Date beginTime;
+    @ApiModelProperty(value = "开始创建时间")
+    private Date beginCreateTime;
 
-    @ApiModelProperty(value = "结束时间", example = "2020-10-24")
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private Date endTime;
+    @ApiModelProperty(value = "结束创建时间")
+    private Date endCreateTime;
 
 }

+ 1 - 1
src/main/java/cn/iocoder/dashboard/modules/system/controller/permission/vo/menu/SysMenuBaseVO.java

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

+ 1 - 1
src/main/java/cn/iocoder/dashboard/modules/system/controller/permission/vo/role/SysRoleBaseVO.java

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

+ 1 - 1
src/main/java/cn/iocoder/dashboard/modules/system/dal/dataobject/dept/SysDeptDO.java

@@ -35,7 +35,7 @@ public class SysDeptDO extends BaseDO {
     /**
      * 显示顺序
      */
-    private String sort;
+    private Integer sort;
     /**
      * 负责人
      */

+ 1 - 1
src/main/java/cn/iocoder/dashboard/modules/system/dal/dataobject/dept/SysPostDO.java

@@ -34,7 +34,7 @@ public class SysPostDO extends BaseDO {
     /**
      * 岗位排序
      */
-    private String sort;
+    private Integer sort;
     /**
      * 状态
      *

+ 1 - 1
src/main/java/cn/iocoder/dashboard/modules/system/dal/dataobject/dict/SysDictTypeDO.java

@@ -33,7 +33,7 @@ public class SysDictTypeDO extends BaseDO {
     /**
      * 字典类型
      */
-    @TableField("dict_type")
+    @TableField("`type`")
     private String type;
     /**
      * 状态

+ 1 - 1
src/main/java/cn/iocoder/dashboard/modules/system/dal/dataobject/permission/SysMenuDO.java

@@ -49,7 +49,7 @@ public class SysMenuDO extends BaseDO {
     /**
      * 显示顺序
      */
-    private String sort;
+    private Integer sort;
     /**
      * 父菜单ID
      */

+ 3 - 3
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dict/SysDictDataMapper.java

@@ -15,13 +15,13 @@ import java.util.List;
 @Mapper
 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)
-                .eq("label", label));
+                .eq("value", value));
     }
 
     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) {

+ 8 - 7
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dict/SysDictTypeMapper.java

@@ -16,20 +16,21 @@ public interface SysDictTypeMapper extends BaseMapperX<SysDictTypeDO> {
     default PageResult<SysDictTypeDO> selectPage(SysDictTypePageReqVO reqVO) {
         return selectPage(reqVO, new QueryWrapperX<SysDictTypeDO>()
                 .likeIfPresent("name", reqVO.getName())
-                .likeIfPresent("dict_type", reqVO.getType())
+                .likeIfPresent("`type`", reqVO.getType())
                 .eqIfPresent("status", reqVO.getStatus())
-                .betweenIfPresent("create_time", reqVO.getBeginTime(), reqVO.getEndTime()));
+                .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime()));
     }
 
     default List<SysDictTypeDO> selectList(SysDictTypeExportReqVO reqVO) {
-        return selectList(new QueryWrapperX<SysDictTypeDO>().likeIfPresent("name", reqVO.getName())
-                        .likeIfPresent("dict_type", reqVO.getType())
-                        .eqIfPresent("status", reqVO.getStatus())
-                        .betweenIfPresent("create_time", reqVO.getBeginTime(), reqVO.getEndTime()));
+        return selectList(new QueryWrapperX<SysDictTypeDO>()
+                .likeIfPresent("name", reqVO.getName())
+                .likeIfPresent("`type`", reqVO.getType())
+                .eqIfPresent("status", reqVO.getStatus())
+                .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime()));
     }
 
     default SysDictTypeDO selectByType(String type) {
-        return selectOne(new QueryWrapperX<SysDictTypeDO>().eq("dict_type", type));
+        return selectOne(new QueryWrapperX<SysDictTypeDO>().eq("`type`", type));
     }
 
     default SysDictTypeDO selectByName(String name) {

+ 2 - 2
src/main/java/cn/iocoder/dashboard/modules/system/enums/SysErrorCodeConstants.java

@@ -58,14 +58,14 @@ public interface SysErrorCodeConstants {
     ErrorCode POST_CODE_DUPLICATE = new ErrorCode(1002005001, "已经存在该标识的岗位");
 
     // ========== 字典类型 1002006000 ==========
-    ErrorCode DICT_TYPE_NOT_FOUND = new ErrorCode(1002006001, "当前字典类型不存在");
+    ErrorCode DICT_TYPE_NOT_EXISTS = new ErrorCode(1002006001, "当前字典类型不存在");
     ErrorCode DICT_TYPE_NOT_ENABLE = new ErrorCode(1002006002, "字典类型不处于开启状态,不允许选择");
     ErrorCode DICT_TYPE_NAME_DUPLICATE = new ErrorCode(1002006003, "已经存在该名字的字典类型");
     ErrorCode DICT_TYPE_TYPE_DUPLICATE = new ErrorCode(1002006004, "已经存在该类型的字典类型");
     ErrorCode DICT_TYPE_HAS_CHILDREN = new ErrorCode(1002006004, "无法删除,该字典类型还有字典数据");
 
     // ========== 字典数据 1002007000 ==========
-    ErrorCode DICT_DATA_NOT_FOUND = new ErrorCode(1002007001, "当前字典数据不存在");
+    ErrorCode DICT_DATA_NOT_EXISTS = new ErrorCode(1002007001, "当前字典数据不存在");
     ErrorCode DICT_DATA_NOT_ENABLE = new ErrorCode(1002007002, "字典数据不处于开启状态,不允许选择");
     ErrorCode DICT_DATA_VALUE_DUPLICATE = new ErrorCode(1002007003, "已经存在该值的字典数据");
 

+ 3 - 3
src/main/java/cn/iocoder/dashboard/modules/system/service/dict/SysDictDataService.java

@@ -27,7 +27,7 @@ public interface SysDictDataService extends DictDataFrameworkService {
      *
      * @return 字典数据全列表
      */
-    List<SysDictDataDO> listDictDatas();
+    List<SysDictDataDO> getDictDataList();
 
     /**
      * 获得字典数据分页列表
@@ -35,7 +35,7 @@ public interface SysDictDataService extends DictDataFrameworkService {
      * @param reqVO 分页请求
      * @return 字典数据分页列表
      */
-    PageResult<SysDictDataDO> pageDictDatas(SysDictDataPageReqVO reqVO);
+    PageResult<SysDictDataDO> getDictDataPage(SysDictDataPageReqVO reqVO);
 
     /**
      * 获得字典数据列表
@@ -43,7 +43,7 @@ public interface SysDictDataService extends DictDataFrameworkService {
      * @param reqVO 列表请求
      * @return 字典数据列表
      */
-    List<SysDictDataDO> listDictDatas(SysDictDataExportReqVO reqVO);
+    List<SysDictDataDO> getDictDataList(SysDictDataExportReqVO reqVO);
 
     /**
      * 获得字典数据详情

+ 3 - 3
src/main/java/cn/iocoder/dashboard/modules/system/service/dict/SysDictTypeService.java

@@ -22,7 +22,7 @@ public interface SysDictTypeService {
      * @param reqVO 分页请求
      * @return 字典类型分页列表
      */
-    PageResult<SysDictTypeDO> pageDictTypes(SysDictTypePageReqVO reqVO);
+    PageResult<SysDictTypeDO> getDictTypePage(SysDictTypePageReqVO reqVO);
 
     /**
      * 获得字典类型列表
@@ -30,7 +30,7 @@ public interface SysDictTypeService {
      * @param reqVO 列表请求
      * @return 字典类型列表
      */
-    List<SysDictTypeDO> listDictTypes(SysDictTypeExportReqVO reqVO);
+    List<SysDictTypeDO> getDictTypeList(SysDictTypeExportReqVO reqVO);
 
     /**
      * 获得字典类型详情
@@ -75,6 +75,6 @@ public interface SysDictTypeService {
      *
      * @return 字典类型列表
      */
-    List<SysDictTypeDO> listDictTypes();
+    List<SysDictTypeDO> getDictTypeList();
 
 }

+ 23 - 19
src/main/java/cn/iocoder/dashboard/modules/system/service/dict/impl/SysDictDataServiceImpl.java

@@ -2,7 +2,6 @@ package cn.iocoder.dashboard.modules.system.service.dict.impl;
 
 import cn.hutool.core.collection.CollUtil;
 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.framework.mybatis.core.dataobject.BaseDO;
 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.SysDictDataUpdateReqVO;
 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.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.service.dict.SysDictDataService;
 import cn.iocoder.dashboard.modules.system.service.dict.SysDictTypeService;
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableTable;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.scheduling.annotation.Scheduled;
@@ -28,6 +28,7 @@ import java.util.Comparator;
 import java.util.Date;
 import java.util.List;
 
+import static cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
 
 /**
@@ -130,19 +131,19 @@ public class SysDictDataServiceImpl implements SysDictDataService {
     }
 
     @Override
-    public List<SysDictDataDO> listDictDatas() {
+    public List<SysDictDataDO> getDictDataList() {
         List<SysDictDataDO> list = dictDataMapper.selectList();
         list.sort(COMPARATOR_TYPE_AND_SORT);
         return list;
     }
 
     @Override
-    public PageResult<SysDictDataDO> pageDictDatas(SysDictDataPageReqVO reqVO) {
+    public PageResult<SysDictDataDO> getDictDataPage(SysDictDataPageReqVO reqVO) {
         return dictDataMapper.selectPage(reqVO);
     }
 
     @Override
-    public List<SysDictDataDO> listDictDatas(SysDictDataExportReqVO reqVO) {
+    public List<SysDictDataDO> getDictDataList(SysDictDataExportReqVO reqVO) {
         List<SysDictDataDO> list = dictDataMapper.selectList(reqVO);
         list.sort(COMPARATOR_TYPE_AND_SORT);
         return list;
@@ -156,7 +157,7 @@ public class SysDictDataServiceImpl implements SysDictDataService {
     @Override
     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);
         dictDataMapper.insert(dictData);
@@ -168,7 +169,7 @@ public class SysDictDataServiceImpl implements SysDictDataService {
     @Override
     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);
         dictDataMapper.updateById(updateObj);
@@ -191,46 +192,49 @@ public class SysDictDataServiceImpl implements SysDictDataService {
         return dictDataMapper.selectCountByDictType(dictType);
     }
 
-    private void checkCreateOrUpdate(Long id, String label, String dictType) {
+    private void checkCreateOrUpdate(Long id, String value, String dictType) {
         // 校验自己存在
         checkDictDataExists(id);
-        // 校验字典数据的值的唯一性
-        checkDictDataValueUnique(id, dictType, label);
         // 校验字典类型有效
         checkDictTypeValid(dictType);
+        // 校验字典数据的值的唯一性
+        checkDictDataValueUnique(id, dictType, value);
     }
 
-    private void checkDictDataValueUnique(Long id, String dictType, String label) {
-        SysDictDataDO dictData = dictDataMapper.selectByDictTypeAndLabel(dictType, label);
+    @VisibleForTesting
+    public void checkDictDataValueUnique(Long id, String dictType, String value) {
+        SysDictDataDO dictData = dictDataMapper.selectByDictTypeAndValue(dictType, value);
         if (dictData == null) {
             return;
         }
         // 如果 id 为空,说明不用比较是否为相同 id 的字典数据
         if (id == null) {
-            throw ServiceExceptionUtil.exception(DICT_DATA_VALUE_DUPLICATE);
+            throw exception(DICT_DATA_VALUE_DUPLICATE);
         }
         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) {
             return;
         }
         SysDictDataDO dictData = dictDataMapper.selectById(id);
         if (dictData == null) {
-            throw ServiceExceptionUtil.exception(DICT_DATA_NOT_FOUND);
+            throw exception(DICT_DATA_NOT_EXISTS);
         }
     }
 
-    private void checkDictTypeValid(String type) {
+    @VisibleForTesting
+    public void checkDictTypeValid(String type) {
         SysDictTypeDO dictType = dictTypeService.getDictType(type);
         if (dictType == null) {
-            throw ServiceExceptionUtil.exception(DICT_TYPE_NOT_FOUND);
+            throw exception(DICT_TYPE_NOT_EXISTS);
         }
         if (!CommonStatusEnum.ENABLE.getStatus().equals(dictType.getStatus())) {
-            throw ServiceExceptionUtil.exception(DICT_TYPE_NOT_ENABLE);
+            throw exception(DICT_TYPE_NOT_ENABLE);
         }
     }
 

+ 19 - 15
src/main/java/cn/iocoder/dashboard/modules/system/service/dict/impl/SysDictTypeServiceImpl.java

@@ -1,21 +1,22 @@
 package cn.iocoder.dashboard.modules.system.service.dict.impl;
 
-import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil;
 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.SysDictTypeExportReqVO;
 import cn.iocoder.dashboard.modules.system.controller.dict.vo.type.SysDictTypePageReqVO;
 import cn.iocoder.dashboard.modules.system.controller.dict.vo.type.SysDictTypeUpdateReqVO;
 import cn.iocoder.dashboard.modules.system.convert.dict.SysDictTypeConvert;
-import cn.iocoder.dashboard.modules.system.dal.mysql.dict.SysDictTypeMapper;
 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.service.dict.SysDictDataService;
 import cn.iocoder.dashboard.modules.system.service.dict.SysDictTypeService;
+import com.google.common.annotations.VisibleForTesting;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
 import java.util.List;
 
+import static cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
 
 /**
@@ -33,12 +34,12 @@ public class SysDictTypeServiceImpl implements SysDictTypeService {
     private SysDictTypeMapper dictTypeMapper;
 
     @Override
-    public PageResult<SysDictTypeDO> pageDictTypes(SysDictTypePageReqVO reqVO) {
+    public PageResult<SysDictTypeDO> getDictTypePage(SysDictTypePageReqVO reqVO) {
         return dictTypeMapper.selectPage(reqVO);
     }
 
     @Override
-    public List<SysDictTypeDO> listDictTypes(SysDictTypeExportReqVO reqVO) {
+    public List<SysDictTypeDO> getDictTypeList(SysDictTypeExportReqVO reqVO) {
         return dictTypeMapper.selectList(reqVO);
     }
 
@@ -77,14 +78,14 @@ public class SysDictTypeServiceImpl implements SysDictTypeService {
         SysDictTypeDO dictType = this.checkDictTypeExists(id);
         // 校验是否有字典数据
         if (dictDataService.countByDictType(dictType.getType()) > 0) {
-            throw ServiceExceptionUtil.exception(DICT_TYPE_HAS_CHILDREN);
+            throw exception(DICT_TYPE_HAS_CHILDREN);
         }
         // 删除字典类型
         dictTypeMapper.deleteById(id);
     }
 
     @Override
-    public List<SysDictTypeDO> listDictTypes() {
+    public List<SysDictTypeDO> getDictTypeList() {
         return dictTypeMapper.selectList();
     }
 
@@ -97,41 +98,44 @@ public class SysDictTypeServiceImpl implements SysDictTypeService {
         checkDictTypeUnique(id, type);
     }
 
-    private void checkDictTypeNameUnique(Long id, String type) {
-        SysDictTypeDO dictType = dictTypeMapper.selectByName(type);
+    @VisibleForTesting
+    public void checkDictTypeNameUnique(Long id, String name) {
+        SysDictTypeDO dictType = dictTypeMapper.selectByName(name);
         if (dictType == null) {
             return;
         }
         // 如果 id 为空,说明不用比较是否为相同 id 的字典类型
         if (id == null) {
-            throw ServiceExceptionUtil.exception(DICT_TYPE_NAME_DUPLICATE);
+            throw exception(DICT_TYPE_NAME_DUPLICATE);
         }
         if (!dictType.getId().equals(id)) {
-            throw ServiceExceptionUtil.exception(DICT_TYPE_NAME_DUPLICATE);
+            throw exception(DICT_TYPE_NAME_DUPLICATE);
         }
     }
 
-    private void checkDictTypeUnique(Long id, String type) {
+    @VisibleForTesting
+    public void checkDictTypeUnique(Long id, String type) {
         SysDictTypeDO dictType = dictTypeMapper.selectByType(type);
         if (dictType == null) {
             return;
         }
         // 如果 id 为空,说明不用比较是否为相同 id 的字典类型
         if (id == null) {
-            throw ServiceExceptionUtil.exception(DICT_TYPE_TYPE_DUPLICATE);
+            throw exception(DICT_TYPE_TYPE_DUPLICATE);
         }
         if (!dictType.getId().equals(id)) {
-            throw ServiceExceptionUtil.exception(DICT_TYPE_TYPE_DUPLICATE);
+            throw exception(DICT_TYPE_TYPE_DUPLICATE);
         }
     }
 
-    private SysDictTypeDO checkDictTypeExists(Long id) {
+    @VisibleForTesting
+    public SysDictTypeDO checkDictTypeExists(Long id) {
         if (id == null) {
             return null;
         }
         SysDictTypeDO dictType = dictTypeMapper.selectById(id);
         if (dictType == null) {
-            throw ServiceExceptionUtil.exception(DICT_TYPE_NOT_FOUND);
+            throw exception(DICT_TYPE_NOT_EXISTS);
         }
         return dictType;
     }

+ 8 - 5
src/main/java/cn/iocoder/dashboard/modules/tool/service/codegen/impl/ToolCodegenEngine.java

@@ -53,9 +53,7 @@ public class ToolCodegenEngine {
      * value:生成的路径
      */
     private static final Map<String, String> TEMPLATES = MapUtil.<String, String>builder(new LinkedHashMap<>()) // 有序
-            // Java
-            .put(javaTemplatePath("controller/controller"),
-                    javaFilePath("controller/${table.businessName}/${table.className}Controller"))
+            // Java Main
             .put(javaTemplatePath("controller/vo/baseVO"),
                     javaFilePath("controller/${table.businessName}/vo/${table.className}BaseVO"))
             .put(javaTemplatePath("controller/vo/createReqVO"),
@@ -70,6 +68,8 @@ public class ToolCodegenEngine {
                     javaFilePath("controller/${table.businessName}/vo/${table.className}ExportReqVO"))
             .put(javaTemplatePath("controller/vo/excelVO"),
                     javaFilePath("controller/${table.businessName}/vo/${table.className}ExcelVO"))
+            .put(javaTemplatePath("controller/controller"),
+                    javaFilePath("controller/${table.businessName}/${table.className}Controller"))
             .put(javaTemplatePath("convert/convert"),
                     javaFilePath("convert/${table.businessName}/${table.className}Convert"))
             .put(javaTemplatePath("dal/do"),
@@ -78,10 +78,13 @@ public class ToolCodegenEngine {
                     javaFilePath("dal/mysql/${table.businessName}/${table.className}Mapper"))
             .put(javaTemplatePath("enums/errorcode"),
                     javaFilePath("enums/${simpleModuleName_upperFirst}ErrorCodeConstants"))
-            .put(javaTemplatePath("service/service"),
-                    javaFilePath("service/${table.businessName}/${table.className}Service"))
             .put(javaTemplatePath("service/serviceImpl"),
                     javaFilePath("service/${table.businessName}/impl/${table.className}ServiceImpl"))
+            .put(javaTemplatePath("service/service"),
+                    javaFilePath("service/${table.businessName}/${table.className}Service"))
+            // Java Test
+            .put(javaTemplatePath("test/serviceTest"),
+                    javaFilePath("service/${table.businessName}/${table.className}ServiceTest"))
             // Vue
             .put(vueTemplatePath("views/index.vue"),
                     vueFilePath("views/${table.moduleName}/${classNameVar}/index.vue"))

+ 33 - 0
src/main/java/cn/iocoder/dashboard/util/collection/ArrayUtils.java

@@ -0,0 +1,33 @@
+package cn.iocoder.dashboard.util.collection;
+
+import cn.hutool.core.util.ArrayUtil;
+
+import java.util.function.Consumer;
+
+/**
+ * Array 工具类
+ *
+ * @author 芋道源码
+ */
+public class ArrayUtils {
+
+    /**
+     * 将 object 和 newElements 合并成一个数组
+     *
+     * @param object 对象
+     * @param newElements 数组
+     * @param <T> 泛型
+     * @return 结果数组
+     */
+    @SafeVarargs
+    public static <T> Consumer<T>[] append(Consumer<T> object, Consumer<T>... newElements) {
+        if (object == null) {
+            return newElements;
+        }
+        Consumer<T>[] result = ArrayUtil.newArray(Consumer.class, 1 + newElements.length);
+        result[0] = object;
+        System.arraycopy(newElements, 0, result, 1, newElements.length);
+        return result;
+    }
+
+}

+ 37 - 0
src/main/java/cn/iocoder/dashboard/util/date/DateUtils.java

@@ -1,6 +1,7 @@
 package cn.iocoder.dashboard.util.date;
 
 import java.time.Duration;
+import java.util.Calendar;
 import java.util.Date;
 
 /**
@@ -22,4 +23,40 @@ public class DateUtils {
         return endTime.getTime() - startTime.getTime();
     }
 
+    /**
+     * 创建指定时间
+     *
+     * @param year        年
+     * @param mouth       月
+     * @param day         日
+     * @return 指定时间
+     */
+    public static Date buildTime(int year, int mouth, int day) {
+        return buildTime(year, mouth, day, 0, 0, 0);
+    }
+
+    /**
+     * 创建指定时间
+     *
+     * @param year        年
+     * @param mouth       月
+     * @param day         日
+     * @param hour        小时
+     * @param minute      分钟
+     * @param second      秒
+     * @return 指定时间
+     */
+    public static Date buildTime(int year, int mouth, int day,
+                                 int hour, int minute, int second) {
+        Calendar calendar = Calendar.getInstance();
+        calendar.set(Calendar.YEAR, year);
+        calendar.set(Calendar.MONTH, mouth - 1);
+        calendar.set(Calendar.DAY_OF_MONTH, day);
+        calendar.set(Calendar.HOUR_OF_DAY, hour);
+        calendar.set(Calendar.MINUTE, minute);
+        calendar.set(Calendar.SECOND, second);
+        calendar.set(Calendar.MILLISECOND, 0); // 一般情况下,都是 0 毫秒
+        return calendar.getTime();
+    }
+
 }

+ 32 - 0
src/main/java/cn/iocoder/dashboard/util/object/ObjectUtils.java

@@ -0,0 +1,32 @@
+package cn.iocoder.dashboard.util.object;
+
+import cn.hutool.core.util.ObjectUtil;
+
+import java.util.function.Consumer;
+
+/**
+ * Object 工具类
+ *
+ * @author 芋道源码
+ */
+public class ObjectUtils {
+
+    public static <T> T clone(T object, Consumer<T> consumer) {
+        T result = ObjectUtil.clone(object);
+        if (result != null) {
+            consumer.accept(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;
+    }
+
+}

+ 1 - 1
src/main/resources/application-dev.yaml

@@ -154,7 +154,7 @@ yudao:
   file:
     base-path: http://127.0.0.1:${server.port}/${yudao.web.api-prefix}/file/get/
   codegen:
-    base-package: ${yudao.info.base-package}.modules
+    base-package: ${yudao.info.base-package}
     db-schemas: ${spring.datasource.name}
   xss:
     enable: false

+ 1 - 1
src/main/resources/application-local.yaml

@@ -154,7 +154,7 @@ yudao:
   file:
     base-path: http://127.0.0.1:${server.port}/${yudao.web.api-prefix}/file/get/
   codegen:
-    base-package: ${yudao.info.base-package}.modules
+    base-package: ${yudao.info.base-package}
     db-schemas: ${spring.datasource.name}
   xss:
     enable: false

+ 5 - 5
src/main/resources/codegen/java/controller/controller.vm

@@ -1,4 +1,4 @@
-package ${basePackage}.${table.moduleName}.controller.${table.businessName};
+package ${basePackage}.modules.${table.moduleName}.controller.${table.businessName};
 
 import org.springframework.web.bind.annotation.*;
 import javax.annotation.Resource;
@@ -22,10 +22,10 @@ import ${ExcelUtilsClassName};
 import ${OperateLogClassName};
 import static ${OperateTypeEnumClassName}.*;
 
-import ${basePackage}.${table.moduleName}.controller.${table.businessName}.vo.*;
-import ${basePackage}.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
-import ${basePackage}.${table.moduleName}.convert.${table.businessName}.${table.className}Convert;
-import ${basePackage}.${table.moduleName}.service.${table.businessName}.${table.className}Service;
+import ${basePackage}.modules.${table.moduleName}.controller.${table.businessName}.vo.*;
+import ${basePackage}.modules.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
+import ${basePackage}.modules.${table.moduleName}.convert.${table.businessName}.${table.className}Convert;
+import ${basePackage}.modules.${table.moduleName}.service.${table.businessName}.${table.className}Service;
 
 @Api(tags = "${table.classComment}")
 @RestController

+ 1 - 1
src/main/resources/codegen/java/controller/vo/baseVO.vm

@@ -1,4 +1,4 @@
-package ${basePackage}.${table.moduleName}.controller.${table.businessName}.vo;
+package ${basePackage}.modules.${table.moduleName}.controller.${table.businessName}.vo;
 
 import lombok.*;
 import java.util.*;

+ 1 - 1
src/main/resources/codegen/java/controller/vo/createReqVO.vm

@@ -1,4 +1,4 @@
-package ${basePackage}.${table.moduleName}.controller.${table.businessName}.vo;
+package ${basePackage}.modules.${table.moduleName}.controller.${table.businessName}.vo;
 
 import lombok.*;
 import java.util.*;

+ 1 - 1
src/main/resources/codegen/java/controller/vo/excelVO.vm

@@ -1,4 +1,4 @@
-package ${basePackage}.${table.moduleName}.controller.${table.businessName}.vo;
+package ${basePackage}.modules.${table.moduleName}.controller.${table.businessName}.vo;
 
 import lombok.*;
 import java.util.*;

+ 1 - 1
src/main/resources/codegen/java/controller/vo/exportReqVO.vm

@@ -1,4 +1,4 @@
-package ${basePackage}.${table.moduleName}.controller.${table.businessName}.vo;
+package ${basePackage}.modules.${table.moduleName}.controller.${table.businessName}.vo;
 
 import lombok.*;
 import java.util.*;

+ 1 - 1
src/main/resources/codegen/java/controller/vo/pageReqVO.vm

@@ -1,4 +1,4 @@
-package ${basePackage}.${table.moduleName}.controller.${table.businessName}.vo;
+package ${basePackage}.modules.${table.moduleName}.controller.${table.businessName}.vo;
 
 import lombok.*;
 import java.util.*;

+ 1 - 1
src/main/resources/codegen/java/controller/vo/respVO.vm

@@ -1,4 +1,4 @@
-package ${basePackage}.${table.moduleName}.controller.${table.businessName}.vo;
+package ${basePackage}.modules.${table.moduleName}.controller.${table.businessName}.vo;
 
 import lombok.*;
 import java.util.*;

+ 1 - 1
src/main/resources/codegen/java/controller/vo/updateReqVO.vm

@@ -1,4 +1,4 @@
-package ${basePackage}.${table.moduleName}.controller.${table.businessName}.vo;
+package ${basePackage}.modules.${table.moduleName}.controller.${table.businessName}.vo;
 
 import lombok.*;
 import java.util.*;

+ 3 - 3
src/main/resources/codegen/java/convert/convert.vm

@@ -1,4 +1,4 @@
-package ${basePackage}.${table.moduleName}.convert.${table.businessName};
+package ${basePackage}.modules.${table.moduleName}.convert.${table.businessName};
 
 import java.util.*;
 
@@ -6,8 +6,8 @@ import ${PageResultClassName};
 
 import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;
-import ${basePackage}.${table.moduleName}.controller.${table.businessName}.vo.*;
-import ${basePackage}.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
+import ${basePackage}.modules.${table.moduleName}.controller.${table.businessName}.vo.*;
+import ${basePackage}.modules.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
 
 /**
  * ${table.classComment} Convert

+ 1 - 1
src/main/resources/codegen/java/dal/do.vm

@@ -1,4 +1,4 @@
-package ${basePackage}.${table.moduleName}.dal.dataobject.${table.businessName};
+package ${basePackage}.modules.${table.moduleName}.dal.dataobject.${table.businessName};
 
 import lombok.*;
 import java.util.*;

+ 3 - 3
src/main/resources/codegen/java/dal/mapper.vm

@@ -1,13 +1,13 @@
-package ${basePackage}.${table.moduleName}.dal.mysql.${table.businessName};
+package ${basePackage}.modules.${table.moduleName}.dal.mysql.${table.businessName};
 
 import java.util.*;
 
 import ${PageResultClassName};
 import ${QueryWrapperClassName};
 import ${BaseMapperClassName};
-import ${basePackage}.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
+import ${basePackage}.modules.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
 import org.apache.ibatis.annotations.Mapper;
-import ${basePackage}.${table.moduleName}.controller.${table.businessName}.vo.*;
+import ${basePackage}.modules.${table.moduleName}.controller.${table.businessName}.vo.*;
 
 ## 字段模板
 #macro(listCondition)

+ 3 - 3
src/main/resources/codegen/java/service/service.vm

@@ -1,9 +1,9 @@
-package ${basePackage}.${table.moduleName}.service.${table.businessName};
+package ${basePackage}.modules.${table.moduleName}.service.${table.businessName};
 
 import java.util.*;
 import javax.validation.*;
-import ${basePackage}.${table.moduleName}.controller.${table.businessName}.vo.*;
-import ${basePackage}.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
+import ${basePackage}.modules.${table.moduleName}.controller.${table.businessName}.vo.*;
+import ${basePackage}.modules.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
 import ${PageResultClassName};
 
 /**

+ 7 - 7
src/main/resources/codegen/java/service/serviceImpl.vm

@@ -1,4 +1,4 @@
-package ${basePackage}.${table.moduleName}.service.${table.businessName}.impl;
+package ${basePackage}.modules.${table.moduleName}.service.${table.businessName}.impl;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -6,17 +6,17 @@ import javax.annotation.Resource;
 import org.springframework.validation.annotation.Validated;
 
 import java.util.*;
-import ${basePackage}.${table.moduleName}.controller.${table.businessName}.vo.*;
-import ${basePackage}.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
+import ${basePackage}.modules.${table.moduleName}.controller.${table.businessName}.vo.*;
+import ${basePackage}.modules.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
 import ${PageResultClassName};
 
-import ${basePackage}.${table.moduleName}.convert.${table.businessName}.${table.className}Convert;
-import ${basePackage}.${table.moduleName}.dal.mysql.${table.businessName}.${table.className}Mapper;
-import ${basePackage}.${table.moduleName}.service.${table.businessName}.${table.className}Service;
+import ${basePackage}.modules.${table.moduleName}.convert.${table.businessName}.${table.className}Convert;
+import ${basePackage}.modules.${table.moduleName}.dal.mysql.${table.businessName}.${table.className}Mapper;
+import ${basePackage}.modules.${table.moduleName}.service.${table.businessName}.${table.className}Service;
 
 import ${ServiceExceptionUtilClassName};
 
-import static ${basePackage}.${table.moduleName}.enums.${simpleModuleName_upperFirst}ErrorCodeConstants.*;
+import static ${basePackage}.modules.${table.moduleName}.enums.${simpleModuleName_upperFirst}ErrorCodeConstants.*;
 
 /**
  * ${table.classComment} Service 实现类

+ 161 - 0
src/main/resources/codegen/java/test/serviceTest.vm

@@ -0,0 +1,161 @@
+package ${basePackage}.modules.${table.moduleName}.service.${table.businessName};
+
+import ${basePackage}.BaseSpringBootUnitTest;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+import javax.annotation.Resource;
+
+import cn.iocoder.dashboard.BaseSpringBootUnitTest;
+import ${basePackage}.modules.${table.moduleName}.service.${table.businessName}.impl.${table.className}ServiceImpl;
+import ${basePackage}.modules.${table.moduleName}.controller.${table.businessName}.vo.*;
+import ${basePackage}.modules.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
+import ${basePackage}.modules.${table.moduleName}.dal.mysql.${table.businessName}.${table.className}Mapper;
+import ${basePackage}.util.object.ObjectUtils;
+import ${PageResultClassName};
+
+import javax.annotation.Resource;
+import java.util.*;
+
+import static cn.hutool.core.util.RandomUtil.*;
+import static ${basePackage}.modules.${table.moduleName}.enums.${simpleModuleName_upperFirst}ErrorCodeConstants.*;
+import static ${basePackage}.util.AssertUtils.*;
+import static ${basePackage}.util.RandomUtils.*;
+import static ${basePackage}.util.date.DateUtils.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+## 字段模板
+#macro(getPageCondition $VO)
+       // mock 数据
+       ${table.className}DO db${simpleClassName} = randomPojo(${table.className}DO.class, o -> { // 等会查询到
+       #foreach ($column in $columns)
+       #if (${column.listOperation})
+       #set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写
+           o.set$JavaField(null);
+       #end
+       #end
+       });
+       ${classNameVar}Mapper.insert(db${simpleClassName});
+       #foreach ($column in $columns)
+       #if (${column.listOperation})
+       #set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写
+       // 测试 ${column.javaField} 不匹配
+       ${classNameVar}Mapper.insert(ObjectUtils.clone(db${simpleClassName}, o -> o.set$JavaField(null)));
+       #end
+       #end
+       // 准备参数
+       ${table.className}${VO} reqVO = new ${table.className}${VO}();
+       #foreach ($column in $columns)
+       #if (${column.listOperation})
+       #set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写
+       #if (${column.listOperationCondition} == "BETWEEN")## BETWEEN 的情况
+       reqVO.setBegin${JavaField}(null);
+       reqVO.setEnd${JavaField}(null);
+       #else
+       reqVO.set$JavaField(null);
+       #end
+       #end
+       #end
+#end
+/**
+* {@link ${table.className}ServiceImpl} 的单元测试类
+*
+* @author ${table.author}
+*/
+public class ${table.className}ServiceTest extends BaseSpringBootUnitTest {
+
+    @Resource
+    private ${table.className}ServiceImpl ${classNameVar}Service;
+
+    @Resource
+    private ${table.className}Mapper ${classNameVar}Mapper;
+
+    @Test
+    public void testCreate${simpleClassName}_success() {
+        // 准备参数
+        ${table.className}CreateReqVO reqVO = randomPojo(${table.className}CreateReqVO.class);
+
+        // 调用
+        Long ${classNameVar}Id = ${classNameVar}Service.create${simpleClassName}(reqVO);
+        // 断言
+        assertNotNull(${classNameVar}Id);
+        // 校验记录的属性是否正确
+        ${table.className}DO ${classNameVar} = ${classNameVar}Mapper.selectById(${classNameVar}Id);
+        assertPojoEquals(reqVO, ${classNameVar});
+    }
+
+    @Test
+    public void testUpdate${simpleClassName}_success() {
+        // mock 数据
+        ${table.className}DO db${simpleClassName} = randomPojo(${table.className}DO.class);
+        ${classNameVar}Mapper.insert(db${simpleClassName});// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        ${table.className}UpdateReqVO reqVO = randomPojo(${table.className}UpdateReqVO.class, o -> {
+            o.setId(db${simpleClassName}.getId()); // 设置更新的 ID
+        });
+
+        // 调用
+        ${classNameVar}Service.update${simpleClassName}(reqVO);
+        // 校验是否更新正确
+        ${table.className}DO ${classNameVar} = ${classNameVar}Mapper.selectById(reqVO.getId()); // 获取最新的
+        assertPojoEquals(reqVO, ${classNameVar});
+    }
+
+    @Test
+    public void testUpdate${simpleClassName}_notExists() {
+        // 准备参数
+        ${table.className}UpdateReqVO reqVO = randomPojo(${table.className}UpdateReqVO.class);
+
+        // 调用, 并断言异常
+        assertServiceException(() -> ${classNameVar}Service.update${simpleClassName}(reqVO), ${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS);
+    }
+
+    @Test
+    public void testDelete${simpleClassName}_success() {
+        // mock 数据
+        ${table.className}DO db${simpleClassName} = randomPojo(${table.className}DO.class);
+        ${classNameVar}Mapper.insert(db${simpleClassName});// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = db${simpleClassName}.getId();
+
+        // 调用
+        ${classNameVar}Service.delete${simpleClassName}(id);
+       // 校验数据不存在了
+       assertNull(${classNameVar}Mapper.selectById(id));
+    }
+
+    @Test
+    public void testDelete${simpleClassName}_notExists() {
+        // 准备参数
+        Long id = randomLongId();
+
+        // 调用, 并断言异常
+        assertServiceException(() -> ${classNameVar}Service.delete${simpleClassName}(id), ${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS);
+    }
+
+    @Test // TODO 请修改 null 为需要的值
+    public void testGet${simpleClassName}Page() {
+       #getPageCondition("PageReqVO")
+
+       // 调用
+       PageResult<${table.className}DO> pageResult = ${classNameVar}Service.get${simpleClassName}Page(reqVO);
+       // 断言
+       assertEquals(1, pageResult.getTotal());
+       assertEquals(1, pageResult.getList().size());
+       assertPojoEquals(db${simpleClassName}, pageResult.getList().get(0));
+    }
+
+    @Test // TODO 请修改 null 为需要的值
+    public void testGet${simpleClassName}List() {
+       #getPageCondition("ExportReqVO")
+
+       // 调用
+       List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}List(reqVO);
+       // 断言
+       assertEquals(1, list.size());
+       assertPojoEquals(db${simpleClassName}, list.get(0));
+    }
+
+}

+ 0 - 103
src/test/java/cn/iocoder/dashboard/modules/infra/service/config/InfConfigServiceImplTest.java

@@ -1,103 +0,0 @@
-package cn.iocoder.dashboard.modules.infra.service.config;
-
-import cn.iocoder.dashboard.BaseSpringBootUnitTest;
-import cn.iocoder.dashboard.common.exception.ServiceException;
-import cn.iocoder.dashboard.modules.infra.controller.config.vo.InfConfigCreateReqVO;
-import cn.iocoder.dashboard.modules.infra.controller.config.vo.InfConfigUpdateReqVO;
-import cn.iocoder.dashboard.modules.infra.dal.dataobject.config.InfConfigDO;
-import cn.iocoder.dashboard.modules.infra.dal.mysql.config.InfConfigMapper;
-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.service.config.impl.InfConfigServiceImpl;
-import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.mock.mockito.MockBean;
-
-import javax.annotation.Resource;
-import java.util.function.Consumer;
-
-import static cn.hutool.core.util.RandomUtil.randomEle;
-import static cn.iocoder.dashboard.modules.infra.enums.InfErrorCodeConstants.CONFIG_KEY_DUPLICATE;
-import static cn.iocoder.dashboard.util.AssertUtils.assertPojoEquals;
-import static cn.iocoder.dashboard.util.RandomUtils.randomPojo;
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-public class InfConfigServiceImplTest extends BaseSpringBootUnitTest {
-
-    @Resource
-    private InfConfigServiceImpl configService;
-
-    @Resource
-    private InfConfigMapper configMapper;
-
-    @MockBean
-    private InfConfigProducer configProducer;
-
-    @Test
-    public void testCreateConfig_success() {
-        // 准备参数
-        InfConfigCreateReqVO reqVO = randomInfConfigCreateReqVO();
-        // mock
-
-        // 调用
-        Long configId = configService.createConfig(reqVO);
-        // 断言
-        assertNotNull(configId);
-        // 校验记录的属性是否正确
-        InfConfigDO config = configMapper.selectById(configId);
-        assertPojoEquals(reqVO, config);
-        assertEquals(InfConfigTypeEnum.CUSTOM.getType(), config.getType());
-        // 校验调用
-        verify(configProducer, times(1)).sendConfigRefreshMessage();
-    }
-
-    @Test
-    public void testCreateConfig_keyDuplicate() {
-        // 准备参数
-        InfConfigCreateReqVO reqVO = randomInfConfigCreateReqVO();
-        // mock 数据
-        configMapper.insert(randomInfConfigDO(o -> { // @Sql
-            o.setKey(reqVO.getKey()); // 模拟 key 重复
-        }));
-
-        // 调用
-        ServiceException serviceException = assertThrows(ServiceException.class, () -> configService.createConfig(reqVO));
-        // 断言
-        assertPojoEquals(CONFIG_KEY_DUPLICATE, serviceException);
-    }
-
-    @Test
-    public void testUpdateConfig_success() {
-        // 准备参数
-        InfConfigUpdateReqVO reqVO = randomInfConfigUpdateReqVO();
-        // mock 数据
-        configMapper.insert(randomInfConfigDO(o -> o.setId(reqVO.getId())));// @Sql: 先插入出一条存在的数据
-
-        // 调用
-        configService.updateConfig(reqVO);
-        // 校验是否更新正确
-        InfConfigDO config = configMapper.selectById(reqVO.getId()); // 获取最新的
-        assertPojoEquals(reqVO, config);
-        // 校验调用
-        verify(configProducer, times(1)).sendConfigRefreshMessage();
-    }
-
-    // ========== 随机对象 ==========
-
-    @SafeVarargs
-    private static InfConfigDO randomInfConfigDO(Consumer<InfConfigDO>... consumers) {
-        InfConfigDO config = randomPojo(InfConfigDO.class, consumers);
-        config.setType(randomEle(InfConfigTypeEnum.values()).getType());
-        return config;
-    }
-
-    private static InfConfigCreateReqVO randomInfConfigCreateReqVO() {
-        return randomPojo(InfConfigCreateReqVO.class);
-    }
-
-    private static InfConfigUpdateReqVO randomInfConfigUpdateReqVO() {
-        return randomPojo(InfConfigUpdateReqVO.class);
-    }
-
-}

+ 254 - 0
src/test/java/cn/iocoder/dashboard/modules/infra/service/config/InfConfigServiceTest.java

@@ -0,0 +1,254 @@
+package cn.iocoder.dashboard.modules.infra.service.config;
+
+import cn.iocoder.dashboard.BaseSpringBootUnitTest;
+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.InfConfigExportReqVO;
+import cn.iocoder.dashboard.modules.infra.controller.config.vo.InfConfigPageReqVO;
+import cn.iocoder.dashboard.modules.infra.controller.config.vo.InfConfigUpdateReqVO;
+import cn.iocoder.dashboard.modules.infra.dal.dataobject.config.InfConfigDO;
+import cn.iocoder.dashboard.modules.infra.dal.mysql.config.InfConfigMapper;
+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.service.config.impl.InfConfigServiceImpl;
+import cn.iocoder.dashboard.util.collection.ArrayUtils;
+import cn.iocoder.dashboard.util.object.ObjectUtils;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.function.Consumer;
+
+import static cn.hutool.core.util.RandomUtil.randomEle;
+import static cn.iocoder.dashboard.modules.infra.enums.InfErrorCodeConstants.*;
+import static cn.iocoder.dashboard.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.dashboard.util.AssertUtils.assertServiceException;
+import static cn.iocoder.dashboard.util.RandomUtils.*;
+import static cn.iocoder.dashboard.util.date.DateUtils.buildTime;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+/**
+ * {@link InfConfigServiceImpl} 的单元测试类
+ *
+ * @author 芋道源码
+ */
+public class InfConfigServiceTest extends BaseSpringBootUnitTest {
+
+    @Resource
+    private InfConfigServiceImpl configService;
+
+    @Resource
+    private InfConfigMapper configMapper;
+    @MockBean
+    private InfConfigProducer configProducer;
+
+    @Test
+    public void testGetConfigPage() {
+        // mock 数据
+        InfConfigDO dbConfig = randomInfConfigDO(o -> { // 等会查询到
+            o.setName("芋艿");
+            o.setKey("yunai");
+            o.setType(InfConfigTypeEnum.SYSTEM.getType());
+            o.setCreateTime(buildTime(2021, 2, 1));
+        });
+        configMapper.insert(dbConfig);
+        // 测试 name 不匹配
+        configMapper.insert(ObjectUtils.clone(dbConfig, o -> o.setName("土豆")));
+        // 测试 key 不匹配
+        configMapper.insert(ObjectUtils.clone(dbConfig, o -> o.setKey("tudou")));
+        // 测试 type 不匹配
+        configMapper.insert(ObjectUtils.clone(dbConfig, o -> o.setType(InfConfigTypeEnum.CUSTOM.getType())));
+        // 测试 createTime 不匹配
+        configMapper.insert(ObjectUtils.clone(dbConfig, o -> o.setCreateTime(buildTime(2021, 1, 1))));
+        // 准备参数
+        InfConfigPageReqVO reqVO = new InfConfigPageReqVO();
+        reqVO.setName("艿");
+        reqVO.setKey("nai");
+        reqVO.setType(InfConfigTypeEnum.SYSTEM.getType());
+        reqVO.setBeginTime(buildTime(2021, 1, 15));
+        reqVO.setEndTime(buildTime(2021, 2, 15));
+
+        // 调用
+        PageResult<InfConfigDO> pageResult = configService.getConfigPage(reqVO);
+        // 断言
+        assertEquals(1, pageResult.getTotal());
+        assertEquals(1, pageResult.getList().size());
+        assertPojoEquals(dbConfig, pageResult.getList().get(0));
+    }
+
+    @Test
+    public void testGetConfigList() {
+        // mock 数据
+        InfConfigDO dbConfig = randomInfConfigDO(o -> { // 等会查询到
+            o.setName("芋艿");
+            o.setKey("yunai");
+            o.setType(InfConfigTypeEnum.SYSTEM.getType());
+            o.setCreateTime(buildTime(2021, 2, 1));
+        });
+        configMapper.insert(dbConfig);
+        // 测试 name 不匹配
+        configMapper.insert(ObjectUtils.clone(dbConfig, o -> o.setName("土豆")));
+        // 测试 key 不匹配
+        configMapper.insert(ObjectUtils.clone(dbConfig, o -> o.setKey("tudou")));
+        // 测试 type 不匹配
+        configMapper.insert(ObjectUtils.clone(dbConfig, o -> o.setType(InfConfigTypeEnum.CUSTOM.getType())));
+        // 测试 createTime 不匹配
+        configMapper.insert(ObjectUtils.clone(dbConfig, o -> o.setCreateTime(buildTime(2021, 1, 1))));
+        // 准备参数
+        InfConfigExportReqVO reqVO = new InfConfigExportReqVO();
+        reqVO.setName("艿");
+        reqVO.setKey("nai");
+        reqVO.setType(InfConfigTypeEnum.SYSTEM.getType());
+        reqVO.setBeginTime(buildTime(2021, 1, 15));
+        reqVO.setEndTime(buildTime(2021, 2, 15));
+
+        // 调用
+        List<InfConfigDO> list = configService.getConfigList(reqVO);
+        // 断言
+        assertEquals(1, list.size());
+        assertPojoEquals(dbConfig, list.get(0));
+    }
+
+    @Test
+    public void testGetConfigByKey() {
+        // mock 数据
+        InfConfigDO dbConfig = randomInfConfigDO();
+        configMapper.insert(dbConfig);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        String key = dbConfig.getKey();
+
+        // 调用
+        InfConfigDO config = configService.getConfigByKey(key);
+        // 断言
+        assertNotNull(config);
+        assertPojoEquals(dbConfig, config);
+    }
+
+    @Test
+    public void testCreateConfig_success() {
+        // 准备参数
+        InfConfigCreateReqVO reqVO = randomPojo(InfConfigCreateReqVO.class);
+
+        // 调用
+        Long configId = configService.createConfig(reqVO);
+        // 断言
+        assertNotNull(configId);
+        // 校验记录的属性是否正确
+        InfConfigDO config = configMapper.selectById(configId);
+        assertPojoEquals(reqVO, config);
+        assertEquals(InfConfigTypeEnum.CUSTOM.getType(), config.getType());
+        // 校验调用
+        verify(configProducer, times(1)).sendConfigRefreshMessage();
+    }
+
+    @Test
+    public void testUpdateConfig_success() {
+        // mock 数据
+        InfConfigDO dbConfig = randomInfConfigDO();
+        configMapper.insert(dbConfig);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        InfConfigUpdateReqVO reqVO = randomPojo(InfConfigUpdateReqVO.class, o -> {
+            o.setId(dbConfig.getId()); // 设置更新的 ID
+        });
+
+        // 调用
+        configService.updateConfig(reqVO);
+        // 校验是否更新正确
+        InfConfigDO config = configMapper.selectById(reqVO.getId()); // 获取最新的
+        assertPojoEquals(reqVO, config);
+        // 校验调用
+        verify(configProducer, times(1)).sendConfigRefreshMessage();
+    }
+
+    @Test
+    public void testDeleteConfig_success() {
+        // mock 数据
+        InfConfigDO dbConfig = randomInfConfigDO(o -> {
+            o.setType(InfConfigTypeEnum.CUSTOM.getType()); // 只能删除 CUSTOM 类型
+        });
+        configMapper.insert(dbConfig);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbConfig.getId();
+
+        // 调用
+        configService.deleteConfig(id);
+        // 校验数据不存在了
+        assertNull(configMapper.selectById(id));
+        // 校验调用
+        verify(configProducer, times(1)).sendConfigRefreshMessage();
+    }
+
+    @Test
+    public void testDeleteConfig_canNotDeleteSystemType() {
+        // mock 数据
+        InfConfigDO dbConfig = randomInfConfigDO(o -> {
+            o.setType(InfConfigTypeEnum.SYSTEM.getType()); // SYSTEM 不允许删除
+        });
+        configMapper.insert(dbConfig);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbConfig.getId();
+
+        // 调用, 并断言异常
+        assertServiceException(() -> configService.deleteConfig(id), CONFIG_CAN_NOT_DELETE_SYSTEM_TYPE);
+    }
+
+    @Test
+    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();
+        String key = randomString();
+        // mock 数据
+        configMapper.insert(randomInfConfigDO(o -> o.setKey(key)));
+
+        // 调用,校验异常
+        assertServiceException(() -> configService.checkConfigKeyUnique(id, key),
+                CONFIG_KEY_DUPLICATE);
+    }
+
+    // ========== 随机对象 ==========
+
+    @SafeVarargs
+    private static InfConfigDO randomInfConfigDO(Consumer<InfConfigDO>... consumers) {
+        Consumer<InfConfigDO> consumer = (o) -> {
+            o.setType(randomEle(InfConfigTypeEnum.values()).getType()); // 保证 key 的范围
+        };
+        return randomPojo(InfConfigDO.class, ArrayUtils.append(consumer, consumers));
+    }
+
+}

+ 2 - 2
src/test/java/cn/iocoder/dashboard/modules/system/service/auth/SysAuthServiceImplTest.java

@@ -61,7 +61,7 @@ public class SysAuthServiceImplTest extends BaseSpringBootUnitTest {
     @Test
     public void testMockLogin_success() {
         // 准备参数
-        Long userId = randomLong();
+        Long userId = randomLongId();
         // mock 方法 01
         SysUserDO user = randomUserDO(o -> o.setId(userId));
         when(userService.getUser(eq(userId))).thenReturn(user);
@@ -80,7 +80,7 @@ public class SysAuthServiceImplTest extends BaseSpringBootUnitTest {
     @Test
     public void testMockLogin_userNotFound() {
         // 准备参数
-        Long userId = randomLong();
+        Long userId = randomLongId();
         // mock 方法
 
         // 调用, 并断言异常

+ 300 - 0
src/test/java/cn/iocoder/dashboard/modules/system/service/dict/SysDictDataServiceTest.java

@@ -0,0 +1,300 @@
+package cn.iocoder.dashboard.modules.system.service.dict;
+
+import cn.iocoder.dashboard.BaseSpringBootUnitTest;
+import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
+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.SysDictDataExportReqVO;
+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.dal.dataobject.dict.SysDictDataDO;
+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.service.dict.impl.SysDictDataServiceImpl;
+import cn.iocoder.dashboard.util.collection.ArrayUtils;
+import cn.iocoder.dashboard.util.object.ObjectUtils;
+import com.google.common.collect.ImmutableTable;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+import javax.annotation.Resource;
+import java.util.Date;
+import java.util.List;
+import java.util.function.Consumer;
+
+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.assertServiceException;
+import static cn.iocoder.dashboard.util.RandomUtils.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+
+/**
+* {@link SysDictDataServiceImpl} 的单元测试类
+*
+* @author 芋道源码
+*/
+public class SysDictDataServiceTest extends BaseSpringBootUnitTest {
+
+    @Resource
+    private SysDictDataServiceImpl dictDataService;
+
+    @Resource
+    private SysDictDataMapper dictDataMapper;
+    @MockBean
+    private SysDictTypeService dictTypeService;
+    @MockBean
+    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
+    public void testGetDictDataPage() {
+        // mock 数据
+        SysDictDataDO dbDictData = randomPojo(SysDictDataDO.class, o -> { // 等会查询到
+            o.setLabel("芋艿");
+            o.setDictType("yunai");
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        });
+        dictDataMapper.insert(dbDictData);
+        // 测试 label 不匹配
+        dictDataMapper.insert(ObjectUtils.clone(dbDictData, o -> o.setLabel("艿")));
+        // 测试 dictType 不匹配
+        dictDataMapper.insert(ObjectUtils.clone(dbDictData, o -> o.setDictType("nai")));
+        // 测试 status 不匹配
+        dictDataMapper.insert(ObjectUtils.clone(dbDictData, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
+        // 准备参数
+        SysDictDataPageReqVO reqVO = new SysDictDataPageReqVO();
+        reqVO.setLabel("芋");
+        reqVO.setDictType("yu");
+        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+
+        // 调用
+        PageResult<SysDictDataDO> pageResult = dictDataService.getDictDataPage(reqVO);
+        // 断言
+        assertEquals(1, pageResult.getTotal());
+        assertEquals(1, pageResult.getList().size());
+        assertPojoEquals(dbDictData, pageResult.getList().get(0));
+    }
+
+    @Test
+    public void testGetDictDataList() {
+        // mock 数据
+        SysDictDataDO dbDictData = randomPojo(SysDictDataDO.class, o -> { // 等会查询到
+            o.setLabel("芋艿");
+            o.setDictType("yunai");
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        });
+        dictDataMapper.insert(dbDictData);
+        // 测试 label 不匹配
+        dictDataMapper.insert(ObjectUtils.clone(dbDictData, o -> o.setLabel("艿")));
+        // 测试 dictType 不匹配
+        dictDataMapper.insert(ObjectUtils.clone(dbDictData, o -> o.setDictType("nai")));
+        // 测试 status 不匹配
+        dictDataMapper.insert(ObjectUtils.clone(dbDictData, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
+        // 准备参数
+        SysDictDataExportReqVO reqVO = new SysDictDataExportReqVO();
+        reqVO.setLabel("芋");
+        reqVO.setDictType("yu");
+        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+
+        // 调用
+        List<SysDictDataDO> list = dictDataService.getDictDataList(reqVO);
+        // 断言
+        assertEquals(1, list.size());
+        assertPojoEquals(dbDictData, list.get(0));
+    }
+
+    @Test
+    public void testCreateDictData_success() {
+        // 准备参数
+        SysDictDataCreateReqVO reqVO = randomPojo(SysDictDataCreateReqVO.class,
+                o -> o.setStatus(randomCommonStatus()));
+        // mock 方法
+        when(dictTypeService.getDictType(eq(reqVO.getDictType()))).thenReturn(randomDictTypeDO(reqVO.getDictType()));
+
+        // 调用
+        Long dictDataId = dictDataService.createDictData(reqVO);
+        // 断言
+        assertNotNull(dictDataId);
+        // 校验记录的属性是否正确
+        SysDictDataDO dictData = dictDataMapper.selectById(dictDataId);
+        assertPojoEquals(reqVO, dictData);
+        // 校验调用
+        verify(dictDataProducer, times(1)).sendDictDataRefreshMessage();
+    }
+
+    @Test
+    public void testUpdateDictData_success() {
+        // mock 数据
+        SysDictDataDO dbDictData = randomDictDataDO();
+        dictDataMapper.insert(dbDictData);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        SysDictDataUpdateReqVO reqVO = randomPojo(SysDictDataUpdateReqVO.class, o -> {
+            o.setId(dbDictData.getId()); // 设置更新的 ID
+            o.setStatus(randomCommonStatus());
+        });
+        // mock 方法,字典类型
+        when(dictTypeService.getDictType(eq(reqVO.getDictType()))).thenReturn(randomDictTypeDO(reqVO.getDictType()));
+
+        // 调用
+        dictDataService.updateDictData(reqVO);
+        // 校验是否更新正确
+        SysDictDataDO dictData = dictDataMapper.selectById(reqVO.getId()); // 获取最新的
+        assertPojoEquals(reqVO, dictData);
+        // 校验调用
+        verify(dictDataProducer, times(1)).sendDictDataRefreshMessage();
+    }
+
+    @Test
+    public void testDeleteDictData_success() {
+        // mock 数据
+        SysDictDataDO dbDictData = randomDictDataDO();
+        dictDataMapper.insert(dbDictData);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbDictData.getId();
+
+        // 调用
+        dictDataService.deleteDictData(id);
+        // 校验数据不存在了
+        assertNull(dictDataMapper.selectById(id));
+        // 校验调用
+        verify(dictDataProducer, times(1)).sendDictDataRefreshMessage();
+    }
+
+    @Test
+    public void testCheckDictDataExists_success() {
+        // mock 数据
+        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.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 是开启
+        });
+    }
+
+}

+ 278 - 0
src/test/java/cn/iocoder/dashboard/modules/system/service/dict/SysDictTypeServiceTest.java

@@ -0,0 +1,278 @@
+package cn.iocoder.dashboard.modules.system.service.dict;
+
+import cn.iocoder.dashboard.BaseSpringBootUnitTest;
+import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
+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.SysDictTypeExportReqVO;
+import cn.iocoder.dashboard.modules.system.controller.dict.vo.type.SysDictTypePageReqVO;
+import cn.iocoder.dashboard.modules.system.controller.dict.vo.type.SysDictTypeUpdateReqVO;
+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.service.dict.impl.SysDictTypeServiceImpl;
+import cn.iocoder.dashboard.util.collection.ArrayUtils;
+import cn.iocoder.dashboard.util.object.ObjectUtils;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.function.Consumer;
+
+import static cn.hutool.core.util.RandomUtil.randomEle;
+import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
+import static cn.iocoder.dashboard.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.dashboard.util.AssertUtils.assertServiceException;
+import static cn.iocoder.dashboard.util.RandomUtils.*;
+import static cn.iocoder.dashboard.util.RandomUtils.randomString;
+import static cn.iocoder.dashboard.util.date.DateUtils.buildTime;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+/**
+* {@link SysDictTypeServiceImpl} 的单元测试类
+*
+* @author 芋道源码
+*/
+public class SysDictTypeServiceTest extends BaseSpringBootUnitTest {
+
+    @Resource
+    private SysDictTypeServiceImpl dictTypeService;
+
+    @Resource
+    private SysDictTypeMapper dictTypeMapper;
+    @MockBean
+    private SysDictDataService dictDataService;
+
+    @Test
+    public void testGetDictTypePage() {
+       // mock 数据
+       SysDictTypeDO dbDictType = randomPojo(SysDictTypeDO.class, o -> { // 等会查询到
+           o.setName("yunai");
+           o.setType("芋艿");
+           o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+           o.setCreateTime(buildTime(2021, 1, 15));
+       });
+       dictTypeMapper.insert(dbDictType);
+       // 测试 name 不匹配
+       dictTypeMapper.insert(ObjectUtils.clone(dbDictType, o -> o.setName("tudou")));
+       // 测试 type 不匹配
+       dictTypeMapper.insert(ObjectUtils.clone(dbDictType, o -> o.setType("土豆")));
+       // 测试 status 不匹配
+       dictTypeMapper.insert(ObjectUtils.clone(dbDictType, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
+       // 测试 createTime 不匹配
+       dictTypeMapper.insert(ObjectUtils.clone(dbDictType, o -> o.setCreateTime(buildTime(2021, 1, 1))));
+       // 准备参数
+       SysDictTypePageReqVO reqVO = new SysDictTypePageReqVO();
+       reqVO.setName("nai");
+       reqVO.setType("艿");
+       reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+       reqVO.setBeginCreateTime(buildTime(2021, 1, 10));
+       reqVO.setEndCreateTime(buildTime(2021, 1, 20));
+
+       // 调用
+       PageResult<SysDictTypeDO> pageResult = dictTypeService.getDictTypePage(reqVO);
+       // 断言
+       assertEquals(1, pageResult.getTotal());
+       assertEquals(1, pageResult.getList().size());
+       assertPojoEquals(dbDictType, pageResult.getList().get(0));
+    }
+
+    @Test
+    public void testGetDictTypeList() {
+        // mock 数据
+        SysDictTypeDO dbDictType = randomPojo(SysDictTypeDO.class, o -> { // 等会查询到
+            o.setName("yunai");
+            o.setType("芋艿");
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setCreateTime(buildTime(2021, 1, 15));
+        });
+        dictTypeMapper.insert(dbDictType);
+        // 测试 name 不匹配
+        dictTypeMapper.insert(ObjectUtils.clone(dbDictType, o -> o.setName("tudou")));
+        // 测试 type 不匹配
+        dictTypeMapper.insert(ObjectUtils.clone(dbDictType, o -> o.setType("土豆")));
+        // 测试 status 不匹配
+        dictTypeMapper.insert(ObjectUtils.clone(dbDictType, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
+        // 测试 createTime 不匹配
+        dictTypeMapper.insert(ObjectUtils.clone(dbDictType, o -> o.setCreateTime(buildTime(2021, 1, 1))));
+        // 准备参数
+        SysDictTypeExportReqVO reqVO = new SysDictTypeExportReqVO();
+        reqVO.setName("nai");
+        reqVO.setType("艿");
+        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        reqVO.setBeginCreateTime(buildTime(2021, 1, 10));
+        reqVO.setEndCreateTime(buildTime(2021, 1, 20));
+
+        // 调用
+        List<SysDictTypeDO> list = dictTypeService.getDictTypeList(reqVO);
+        // 断言
+        assertEquals(1, list.size());
+        assertPojoEquals(dbDictType, list.get(0));
+    }
+
+    @Test
+    public void testGetDictType() {
+        // mock 数据
+        SysDictTypeDO dbDictType = randomDictTypeDO();
+        dictTypeMapper.insert(dbDictType);
+        // 准备参数
+        String type = dbDictType.getType();
+
+        // 调用
+        SysDictTypeDO dictType = dictTypeService.getDictType(type);
+        // 断言
+        assertNotNull(dictType);
+        assertPojoEquals(dbDictType, dictType);
+    }
+
+    @Test
+    public void testCreateDictType_success() {
+        // 准备参数
+        SysDictTypeCreateReqVO reqVO = randomPojo(SysDictTypeCreateReqVO.class,
+                o -> o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()));
+
+        // 调用
+        Long dictTypeId = dictTypeService.createDictType(reqVO);
+        // 断言
+        assertNotNull(dictTypeId);
+        // 校验记录的属性是否正确
+        SysDictTypeDO dictType = dictTypeMapper.selectById(dictTypeId);
+        assertPojoEquals(reqVO, dictType);
+    }
+
+    @Test
+    public void testUpdateDictType_success() {
+        // mock 数据
+        SysDictTypeDO dbDictType = randomDictTypeDO();
+        dictTypeMapper.insert(dbDictType);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        SysDictTypeUpdateReqVO reqVO = randomPojo(SysDictTypeUpdateReqVO.class, o -> {
+            o.setId(dbDictType.getId()); // 设置更新的 ID
+            o.setStatus(randomEle(CommonStatusEnum.values()).getStatus());
+        });
+
+        // 调用
+        dictTypeService.updateDictType(reqVO);
+        // 校验是否更新正确
+        SysDictTypeDO dictType = dictTypeMapper.selectById(reqVO.getId()); // 获取最新的
+        assertPojoEquals(reqVO, dictType);
+    }
+
+    @Test
+    public void testDeleteDictType_success() {
+        // mock 数据
+        SysDictTypeDO dbDictType = randomDictTypeDO();
+        dictTypeMapper.insert(dbDictType);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbDictType.getId();
+
+        // 调用
+        dictTypeService.deleteDictType(id);
+        // 校验数据不存在了
+        assertNull(dictTypeMapper.selectById(id));
+    }
+
+    @Test
+    public void testDeleteDictType_hasChildren() {
+        // mock 数据
+        SysDictTypeDO dbDictType = randomDictTypeDO();
+        dictTypeMapper.insert(dbDictType);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbDictType.getId();
+        // mock 方法
+        when(dictDataService.countByDictType(eq(dbDictType.getType()))).thenReturn(1);
+
+        // 调用, 并断言异常
+        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
+    private static SysDictTypeDO randomDictTypeDO(Consumer<SysDictTypeDO>... consumers) {
+        Consumer<SysDictTypeDO> consumer = (o) -> {
+            o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
+        };
+        return randomPojo(SysDictTypeDO.class, ArrayUtils.append(consumer, consumers));
+    }
+
+}

+ 0 - 1
src/test/java/cn/iocoder/dashboard/modules/system/service/package-info.java

@@ -1 +0,0 @@
-package cn.iocoder.dashboard.modules.system.service;

+ 13 - 4
src/test/java/cn/iocoder/dashboard/util/AssertUtils.java

@@ -4,11 +4,15 @@ import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.ReflectUtil;
 import cn.iocoder.dashboard.common.exception.ErrorCode;
 import cn.iocoder.dashboard.common.exception.ServiceException;
+import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil;
 import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.function.Executable;
 
 import java.lang.reflect.Field;
 import java.util.Arrays;
 
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
 /**
  * 单元测试,assert 断言工具类
  *
@@ -47,14 +51,19 @@ public class AssertUtils {
     }
 
     /**
-     * 比对抛出的 ServiceException 是否匹配
+     * 执行方法,校验抛出的 Service 是否符合条件
      *
+     * @param executable 业务异常
      * @param errorCode 错误码对象
-     * @param serviceException 业务异常
+     * @param messageParams 消息参数
      */
-    public static void assertPojoEquals(ErrorCode errorCode, ServiceException serviceException) {
+    public static void assertServiceException(Executable executable, ErrorCode errorCode, Object... messageParams) {
+        // 调用方法
+        ServiceException serviceException = assertThrows(ServiceException.class, executable);
+        // 校验错误码
         Assertions.assertEquals(errorCode.getCode(), serviceException.getCode(), "错误码不匹配");
-        Assertions.assertEquals(errorCode.getMessage(), serviceException.getMessage(), "错误提示不匹配");
+        String message = ServiceExceptionUtil.doFormat(errorCode.getCode(), errorCode.getMessage(), messageParams);
+        Assertions.assertEquals(message, serviceException.getMessage(), "错误提示不匹配");
     }
 
 }

+ 6 - 1
src/test/java/cn/iocoder/dashboard/util/RandomUtils.java

@@ -2,6 +2,7 @@ package cn.iocoder.dashboard.util;
 
 import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.RandomUtil;
+import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
 import cn.iocoder.dashboard.modules.system.dal.dataobject.user.SysUserDO;
 import uk.co.jemos.podam.api.PodamFactory;
 import uk.co.jemos.podam.api.PodamFactoryImpl;
@@ -46,7 +47,7 @@ public class RandomUtils {
         return RandomUtil.randomString(RANDOM_STRING_LENGTH);
     }
 
-    public static Long randomLong() {
+    public static Long randomLongId() {
         return RandomUtil.randomLong(0, Long.MAX_VALUE);
     }
 
@@ -67,6 +68,10 @@ public class RandomUtils {
                 .map(i -> randomPojo(clazz)).collect(Collectors.toSet());
     }
 
+    public static Integer randomCommonStatus() {
+        return RandomUtil.randomEle(CommonStatusEnum.values()).getStatus();
+    }
+
     @SafeVarargs
     public static SysUserDO randomUserDO(Consumer<SysUserDO>... consumers) {
         return randomPojo(SysUserDO.class, consumers);

+ 1 - 0
src/test/resources/sql/clean.sql

@@ -7,3 +7,4 @@ DELETE FROM "sys_dict_data";
 DELETE FROM "sys_role";
 DELETE FROM "sys_role_menu";
 DELETE FROM "sys_menu";
+DELETE FROM "sys_dict_type";

+ 9 - 9
src/test/resources/sql/create_tables.sql

@@ -101,16 +101,16 @@ CREATE TABLE IF NOT EXISTS "sys_menu" (
     PRIMARY KEY ("id")
 ) COMMENT '菜单权限表';
 
-CREATE TABLE IF NOT EXISTS "sys_notice" (
+CREATE TABLE "sys_dict_type" (
     "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
-    "title" varchar(50) NOT NULL,
-    "content" text NOT NULL,
-    "notice_type" tinyint(4) NOT NULL,
-    "status" tinyint(4) NOT NULL DEFAULT '0',
+    "name" varchar(100) NOT NULL DEFAULT '',
+    "type" varchar(100) NOT NULL DEFAULT '',
+    "status" tinyint NOT NULL DEFAULT '0',
+    "remark" varchar(500) DEFAULT NULL,
     "create_by" varchar(64) DEFAULT '',
-    "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
     "update_by" varchar(64) DEFAULT '',
-    "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-    "deleted" bit NOT NULL DEFAULT '0',
+    "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "deleted" bit NOT NULL DEFAULT FALSE,
     PRIMARY KEY ("id")
-) COMMENT='通知公告表';
+) COMMENT '字典类型表';

Some files were not shown because too many files changed in this diff