Explorar o código

!783 ✅ 增加 system 和 infra 的代码覆盖率,提升稳定性
Merge pull request !783 from 芋道源码/feature/vo-optimize

芋道源码 hai 1 ano
pai
achega
9a898a81fd
Modificáronse 53 ficheiros con 1767 adicións e 447 borrados
  1. 4 3
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/CodegenController.java
  2. 4 56
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/vo/CodegenUpdateReqVO.java
  3. 55 5
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/vo/column/CodegenColumnRespVO.java
  4. 6 6
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/vo/column/CodegenColumnSaveReqVO.java
  5. 49 5
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/vo/table/CodegenTableRespVO.java
  6. 32 5
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/vo/table/CodegenTableSaveReqVO.java
  7. 6 30
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/convert/codegen/CodegenConvert.java
  8. 1 2
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenService.java
  9. 23 21
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenServiceImpl.java
  10. 0 10
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/job/JobService.java
  11. 0 5
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/job/JobServiceImpl.java
  12. 1 1
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm
  13. 556 0
      yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenServiceImplTest.java
  14. 89 0
      yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenBuilderTest.java
  15. 0 4
      yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/package-info.java
  16. 25 0
      yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/job/JobLogServiceImplTest.java
  17. 23 2
      yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/logger/ApiAccessLogServiceImplTest.java
  18. 27 6
      yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/logger/ApiErrorLogServiceImplTest.java
  19. 4 4
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_master_erp/vue/index
  20. 4 4
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_master_inner/vue/index
  21. 4 4
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_master_normal/vue/index
  22. 4 4
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_one/vue/index
  23. 1 1
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_tree/vue/index
  24. 2 0
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/sql/clean.sql
  25. 56 0
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/sql/create_tables.sql
  26. 3 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dict/DictDataController.java
  27. 1 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/vo/user/SocialUserPageReqVO.java
  28. 1 2
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java
  29. 3 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/app/dict/AppDictDataController.java
  30. 4 4
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/dict/DictDataMapper.java
  31. 4 8
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/PostService.java
  32. 9 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/PostServiceImpl.java
  33. 6 10
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictDataService.java
  34. 3 10
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictDataServiceImpl.java
  35. 1 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictTypeServiceImpl.java
  36. 1 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/notify/NotifySendService.java
  37. 2 2
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImpl.java
  38. 0 49
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/bo/RoleCreateReqBO.java
  39. 7 5
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java
  40. 3 6
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java
  41. 18 0
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/dept/PostServiceImplTest.java
  42. 8 3
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/dict/DictDataServiceImplTest.java
  43. 1 1
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/dict/DictTypeServiceImplTest.java
  44. 14 0
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/notify/NotifySendServiceImplTest.java
  45. 36 0
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImplTest.java
  46. 33 0
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImplTest.java
  47. 32 0
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordServiceImplTest.java
  48. 25 10
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsSendServiceImplTest.java
  49. 60 1
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceImplTest.java
  50. 354 29
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImplTest.java
  51. 148 123
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImplTest.java
  52. 12 0
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImplTest.java
  53. 2 1
      yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql

+ 4 - 3
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/CodegenController.java

@@ -4,6 +4,7 @@ import cn.hutool.core.io.IoUtil;
 import cn.hutool.core.util.ZipUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
 import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenCreateListReqVO;
 import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenDetailRespVO;
@@ -66,7 +67,7 @@ public class CodegenController {
     @PreAuthorize("@ss.hasPermission('infra:codegen:query')")
     public CommonResult<List<CodegenTableRespVO>> getCodegenTableList(@RequestParam(value = "dataSourceConfigId") Long dataSourceConfigId) {
         List<CodegenTableDO> list = codegenService.getCodegenTableList(dataSourceConfigId);
-        return success(CodegenConvert.INSTANCE.convertList05(list));
+        return success(BeanUtils.toBean(list, CodegenTableRespVO.class));
     }
 
     @GetMapping("/table/page")
@@ -74,7 +75,7 @@ public class CodegenController {
     @PreAuthorize("@ss.hasPermission('infra:codegen:query')")
     public CommonResult<PageResult<CodegenTableRespVO>> getCodegenTablePage(@Valid CodegenTablePageReqVO pageReqVO) {
         PageResult<CodegenTableDO> pageResult = codegenService.getCodegenTablePage(pageReqVO);
-        return success(CodegenConvert.INSTANCE.convertPage(pageResult));
+        return success(BeanUtils.toBean(pageResult, CodegenTableRespVO.class));
     }
 
     @GetMapping("/detail")
@@ -82,7 +83,7 @@ public class CodegenController {
     @Parameter(name = "tableId", description = "表编号", required = true, example = "1024")
     @PreAuthorize("@ss.hasPermission('infra:codegen:query')")
     public CommonResult<CodegenDetailRespVO> getCodegenDetail(@RequestParam("tableId") Long tableId) {
-        CodegenTableDO table = codegenService.getCodegenTablePage(tableId);
+        CodegenTableDO table = codegenService.getCodegenTable(tableId);
         List<CodegenColumnDO> columns = codegenService.getCodegenColumnListByTableId(tableId);
         // 拼装返回
         return success(CodegenConvert.INSTANCE.convert(table, columns));

+ 4 - 56
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/vo/CodegenUpdateReqVO.java

@@ -1,18 +1,11 @@
 package cn.iocoder.yudao.module.infra.controller.admin.codegen.vo;
 
-import cn.hutool.core.util.ObjectUtil;
-import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.column.CodegenColumnBaseVO;
-import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.CodegenTableBaseVO;
-import cn.iocoder.yudao.module.infra.enums.codegen.CodegenSceneEnum;
-import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
-import com.fasterxml.jackson.annotation.JsonIgnore;
+import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.column.CodegenColumnSaveReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.CodegenTableSaveReqVO;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
 
 import javax.validation.Valid;
-import javax.validation.constraints.AssertTrue;
 import javax.validation.constraints.NotNull;
 import java.util.List;
 
@@ -22,55 +15,10 @@ public class CodegenUpdateReqVO {
 
     @Valid // 校验内嵌的字段
     @NotNull(message = "表定义不能为空")
-    private Table table;
+    private CodegenTableSaveReqVO table;
 
     @Valid // 校验内嵌的字段
     @NotNull(message = "字段定义不能为空")
-    private List<Column> columns;
-
-    @Schema(description = "更新表定义")
-    @Data
-    @EqualsAndHashCode(callSuper = true)
-    @ToString(callSuper = true)
-    @Valid
-    public static class Table extends CodegenTableBaseVO {
-
-        @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-        private Long id;
-
-        @AssertTrue(message = "上级菜单不能为空,请前往 [修改生成配置 -> 生成信息] 界面,设置“上级菜单”字段")
-        @JsonIgnore
-        public boolean isParentMenuIdValid() {
-            // 生成场景为管理后台时,必须设置上级菜单,不然生成的菜单 SQL 是无父级菜单的
-            return ObjectUtil.notEqual(getScene(), CodegenSceneEnum.ADMIN.getScene())
-                    || getParentMenuId() != null;
-        }
-
-        @AssertTrue(message = "关联的父表信息不全")
-        @JsonIgnore
-        public boolean isSubValid() {
-            return ObjectUtil.notEqual(getTemplateType(), CodegenTemplateTypeEnum.SUB)
-                    || (ObjectUtil.isAllNotEmpty(getMasterTableId(), getSubJoinColumnId(), getSubJoinMany()));
-        }
-
-        @AssertTrue(message = "关联的树表信息不全")
-        @JsonIgnore
-        public boolean isTreeValid() {
-            return ObjectUtil.notEqual(getTemplateType(), CodegenTemplateTypeEnum.TREE)
-                    || (ObjectUtil.isAllNotEmpty(getTreeParentColumnId(), getTreeNameColumnId()));
-        }
-
-    }
-
-    @Schema(description = "更新表定义")
-    @Data
-    @EqualsAndHashCode(callSuper = true)
-    @ToString(callSuper = true)
-    public static class Column extends CodegenColumnBaseVO {
-
-        @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-        private Long id;
-
-    }
+    private List<CodegenColumnSaveReqVO> columns;
 
 }

+ 55 - 5
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/vo/column/CodegenColumnRespVO.java

@@ -2,20 +2,70 @@ package cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.column;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
 
 import java.time.LocalDateTime;
 
 @Schema(description = "管理后台 - 代码生成字段定义 Response VO")
 @Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class CodegenColumnRespVO extends CodegenColumnBaseVO {
+public class CodegenColumnRespVO {
 
     @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     private Long id;
 
+    @Schema(description = "表编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long tableId;
+
+    @Schema(description = "字段名", requiredMode = Schema.RequiredMode.REQUIRED, example = "user_age")
+    private String columnName;
+
+    @Schema(description = "字段类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "int(11)")
+    private String dataType;
+
+    @Schema(description = "字段描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "年龄")
+    private String columnComment;
+
+    @Schema(description = "是否允许为空", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+    private Boolean nullable;
+
+    @Schema(description = "是否主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
+    private Boolean primaryKey;
+
+    @Schema(description = "是否自增", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+    private Boolean autoIncrement;
+
+    @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    private Integer ordinalPosition;
+
+    @Schema(description = "Java 属性类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "userAge")
+    private String javaType;
+
+    @Schema(description = "Java 属性名", requiredMode = Schema.RequiredMode.REQUIRED, example = "Integer")
+    private String javaField;
+
+    @Schema(description = "字典类型", example = "sys_gender")
+    private String dictType;
+
+    @Schema(description = "数据示例", example = "1024")
+    private String example;
+
+    @Schema(description = "是否为 Create 创建操作的字段", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+    private Boolean createOperation;
+
+    @Schema(description = "是否为 Update 更新操作的字段", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
+    private Boolean updateOperation;
+
+    @Schema(description = "是否为 List 查询操作的字段", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+    private Boolean listOperation;
+
+    @Schema(description = "List 查询操作的条件类型,参见 CodegenColumnListConditionEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "LIKE")
+    private String listOperationCondition;
+
+    @Schema(description = "是否为 List 查询操作的返回字段", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+    private Boolean listOperationResult;
+
+    @Schema(description = "显示类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "input")
+    private String htmlType;
+
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime createTime;
 

+ 6 - 6
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/vo/column/CodegenColumnBaseVO.java → yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/vo/column/CodegenColumnSaveReqVO.java

@@ -5,12 +5,12 @@ import lombok.Data;
 
 import javax.validation.constraints.NotNull;
 
-/**
-* 代码生成字段定义 Base VO,提供给添加、修改、详细的子 VO 使用
-* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
-*/
+@Schema(description = "管理后台 - 代码生成字段定义创建/修改 Request VO")
 @Data
-public class CodegenColumnBaseVO {
+public class CodegenColumnSaveReqVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long id;
 
     @Schema(description = "表编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     @NotNull(message = "表编号不能为空")
@@ -38,7 +38,7 @@ public class CodegenColumnBaseVO {
 
     @Schema(description = "是否自增", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
     @NotNull(message = "是否自增不能为空")
-    private String autoIncrement;
+    private Boolean autoIncrement;
 
     @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
     @NotNull(message = "排序不能为空")

+ 49 - 5
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/vo/table/CodegenTableRespVO.java

@@ -2,20 +2,64 @@ package cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
 
 import java.time.LocalDateTime;
 
 @Schema(description = "管理后台 - 代码生成表定义 Response VO")
 @Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class CodegenTableRespVO extends CodegenTableBaseVO {
+public class CodegenTableRespVO {
 
     @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     private Long id;
 
+    @Schema(description = "生成场景,参见 CodegenSceneEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer scene;
+
+    @Schema(description = "表名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao")
+    private String tableName;
+
+    @Schema(description = "表描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
+    private String tableComment;
+
+    @Schema(description = "备注", example = "我是备注")
+    private String remark;
+
+    @Schema(description = "模块名", requiredMode = Schema.RequiredMode.REQUIRED, example = "system")
+    private String moduleName;
+
+    @Schema(description = "业务名", requiredMode = Schema.RequiredMode.REQUIRED, example = "codegen")
+    private String businessName;
+
+    @Schema(description = "类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "CodegenTable")
+    private String className;
+
+    @Schema(description = "类描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "代码生成器的表定义")
+    private String classComment;
+
+    @Schema(description = "作者", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码")
+    private String author;
+
+    @Schema(description = "模板类型,参见 CodegenTemplateTypeEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer templateType;
+
+    @Schema(description = "前端类型,参见 CodegenFrontTypeEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
+    private Integer frontType;
+
+    @Schema(description = "父菜单编号", example = "1024")
+    private Long parentMenuId;
+
+    @Schema(description = "主表的编号", example = "2048")
+    private Long masterTableId;
+    @Schema(description = "子表关联主表的字段编号", example = "4096")
+    private Long subJoinColumnId;
+    @Schema(description = "主表与子表是否一对多", example = "4096")
+    private Boolean subJoinMany;
+
+    @Schema(description = "树表的父字段编号", example = "8192")
+    private Long treeParentColumnId;
+    @Schema(description = "树表的名字字段编号", example = "16384")
+    private Long treeNameColumnId;
+
     @Schema(description = "主键编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Integer dataSourceConfigId;
 

+ 32 - 5
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/vo/table/CodegenTableBaseVO.java → yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/vo/table/CodegenTableSaveReqVO.java

@@ -1,16 +1,21 @@
 package cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table;
 
+import cn.hutool.core.util.ObjectUtil;
+import cn.iocoder.yudao.module.infra.enums.codegen.CodegenSceneEnum;
+import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
+import javax.validation.constraints.AssertTrue;
 import javax.validation.constraints.NotNull;
 
-/**
- * 代码生成 Base VO,提供给添加、修改、详细的子 VO 使用
- * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
- */
+@Schema(description = "管理后台 - 代码生成表定义创建/修改 Response VO")
 @Data
-public class CodegenTableBaseVO {
+public class CodegenTableSaveReqVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long id;
 
     @Schema(description = "生成场景,参见 CodegenSceneEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     @NotNull(message = "导入类型不能为空")
@@ -70,4 +75,26 @@ public class CodegenTableBaseVO {
     @Schema(description = "树表的名字字段编号", example = "16384")
     private Long treeNameColumnId;
 
+    @AssertTrue(message = "上级菜单不能为空,请前往 [修改生成配置 -> 生成信息] 界面,设置“上级菜单”字段")
+    @JsonIgnore
+    public boolean isParentMenuIdValid() {
+        // 生成场景为管理后台时,必须设置上级菜单,不然生成的菜单 SQL 是无父级菜单的
+        return ObjectUtil.notEqual(getScene(), CodegenSceneEnum.ADMIN.getScene())
+                || getParentMenuId() != null;
+    }
+
+    @AssertTrue(message = "关联的父表信息不全")
+    @JsonIgnore
+    public boolean isSubValid() {
+        return ObjectUtil.notEqual(getTemplateType(), CodegenTemplateTypeEnum.SUB)
+                || (ObjectUtil.isAllNotEmpty(masterTableId, subJoinColumnId, subJoinMany));
+    }
+
+    @AssertTrue(message = "关联的树表信息不全")
+    @JsonIgnore
+    public boolean isTreeValid() {
+        return ObjectUtil.notEqual(templateType, CodegenTemplateTypeEnum.TREE)
+                || (ObjectUtil.isAllNotEmpty(treeParentColumnId, treeNameColumnId));
+    }
+
 }

+ 6 - 30
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/convert/codegen/CodegenConvert.java

@@ -1,12 +1,11 @@
 package cn.iocoder.yudao.module.infra.convert.codegen;
 
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenDetailRespVO;
 import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenPreviewRespVO;
-import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenUpdateReqVO;
 import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.column.CodegenColumnRespVO;
 import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.CodegenTableRespVO;
-import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.DatabaseTableRespVO;
 import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
 import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
 import com.baomidou.mybatisplus.generator.config.po.TableField;
@@ -20,7 +19,6 @@ import org.mapstruct.factory.Mappers;
 
 import java.util.List;
 import java.util.Map;
-import java.util.stream.Collectors;
 
 @Mapper
 public interface CodegenConvert {
@@ -54,40 +52,18 @@ public interface CodegenConvert {
         return jdbcType.name();
     }
 
-    // ========== CodegenTableDO 相关 ==========
-
-    List<CodegenTableRespVO> convertList05(List<CodegenTableDO> list);
-
-    CodegenTableRespVO convert(CodegenTableDO bean);
-
-    PageResult<CodegenTableRespVO> convertPage(PageResult<CodegenTableDO> page);
-
-    // ========== CodegenTableDO 相关 ==========
-
-    List<CodegenColumnRespVO> convertList02(List<CodegenColumnDO> list);
-
-    CodegenTableDO convert(CodegenUpdateReqVO.Table bean);
-
-    List<CodegenColumnDO> convertList03(List<CodegenUpdateReqVO.Column> columns);
-
-    List<DatabaseTableRespVO> convertList04(List<TableInfo> list);
-
     // ========== 其它 ==========
 
     default CodegenDetailRespVO convert(CodegenTableDO table, List<CodegenColumnDO> columns) {
         CodegenDetailRespVO respVO = new CodegenDetailRespVO();
-        respVO.setTable(convert(table));
-        respVO.setColumns(convertList02(columns));
+        respVO.setTable(BeanUtils.toBean(table, CodegenTableRespVO.class));
+        respVO.setColumns(BeanUtils.toBean(columns, CodegenColumnRespVO.class));
         return respVO;
     }
 
     default List<CodegenPreviewRespVO> convert(Map<String, String> codes) {
-        return codes.entrySet().stream().map(entry -> {
-            CodegenPreviewRespVO respVO = new CodegenPreviewRespVO();
-            respVO.setFilePath(entry.getKey());
-            respVO.setCode(entry.getValue());
-            return respVO;
-        }).collect(Collectors.toList());
+        return CollectionUtils.convertList(codes.entrySet(),
+                entry -> new CodegenPreviewRespVO().setFilePath(entry.getKey()).setCode(entry.getValue()));
     }
 
 }

+ 1 - 2
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenService.java

@@ -70,7 +70,7 @@ public interface CodegenService {
      * @param id 表编号
      * @return 表定义
      */
-    CodegenTableDO getCodegenTablePage(Long id);
+    CodegenTableDO getCodegenTable(Long id);
 
     /**
      * 获得指定表的字段定义数组
@@ -91,7 +91,6 @@ public interface CodegenService {
     /**
      * 获得数据库自带的表定义列表
      *
-     *
      * @param dataSourceConfigId 数据源的配置编号
      * @param name 表名称
      * @param comment 表描述

+ 23 - 21
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenServiceImpl.java

@@ -3,12 +3,11 @@ package cn.iocoder.yudao.module.infra.service.codegen;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenCreateListReqVO;
 import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenUpdateReqVO;
 import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.CodegenTablePageReqVO;
 import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.DatabaseTableRespVO;
-import cn.iocoder.yudao.module.infra.convert.codegen.CodegenConvert;
 import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
 import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
 import cn.iocoder.yudao.module.infra.dal.mysql.codegen.CodegenColumnMapper;
@@ -22,6 +21,7 @@ import cn.iocoder.yudao.module.infra.service.db.DatabaseTableService;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import com.baomidou.mybatisplus.generator.config.po.TableField;
 import com.baomidou.mybatisplus.generator.config.po.TableInfo;
+import com.google.common.annotations.VisibleForTesting;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -31,6 +31,8 @@ import java.util.function.BiPredicate;
 import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
 
 /**
@@ -69,7 +71,7 @@ public class CodegenServiceImpl implements CodegenService {
         return ids;
     }
 
-    public Long createCodegen(Long userId, Long dataSourceConfigId, String tableName) {
+    private Long createCodegen(Long userId, Long dataSourceConfigId, String tableName) {
         // 从数据库中,获得数据库表结构
         TableInfo tableInfo = databaseTableService.getTable(dataSourceConfigId, tableName);
         // 导入
@@ -103,7 +105,8 @@ public class CodegenServiceImpl implements CodegenService {
         return table.getId();
     }
 
-    private void validateTableInfo(TableInfo tableInfo) {
+    @VisibleForTesting
+    void validateTableInfo(TableInfo tableInfo) {
         if (tableInfo == null) {
             throw exception(CODEGEN_IMPORT_TABLE_NULL);
         }
@@ -139,10 +142,10 @@ public class CodegenServiceImpl implements CodegenService {
         }
 
         // 更新 table 表定义
-        CodegenTableDO updateTableObj = CodegenConvert.INSTANCE.convert(updateReqVO.getTable());
+        CodegenTableDO updateTableObj = BeanUtils.toBean(updateReqVO.getTable(), CodegenTableDO.class);
         codegenTableMapper.updateById(updateTableObj);
         // 更新 column 字段定义
-        List<CodegenColumnDO> updateColumnObjs = CodegenConvert.INSTANCE.convertList03(updateReqVO.getColumns());
+        List<CodegenColumnDO> updateColumnObjs = BeanUtils.toBean(updateReqVO.getColumns(), CodegenColumnDO.class);
         updateColumnObjs.forEach(updateColumnObj -> codegenColumnMapper.updateById(updateColumnObj));
     }
 
@@ -161,29 +164,28 @@ public class CodegenServiceImpl implements CodegenService {
     }
 
     private void syncCodegen0(Long tableId, TableInfo tableInfo) {
-        // 校验导入的表和字段非空
+        // 1. 校验导入的表和字段非空
         validateTableInfo(tableInfo);
         List<TableField> tableFields = tableInfo.getFields();
 
-        // 构建 CodegenColumnDO 数组,只同步新增的字段
+        // 2. 构建 CodegenColumnDO 数组,只同步新增的字段
         List<CodegenColumnDO> codegenColumns = codegenColumnMapper.selectListByTableId(tableId);
-        Set<String> codegenColumnNames = CollectionUtils.convertSet(codegenColumns, CodegenColumnDO::getColumnName);
+        Set<String> codegenColumnNames = convertSet(codegenColumns, CodegenColumnDO::getColumnName);
 
-        //计算需要修改的字段,插入时重新插入,删除时将原来的删除
-        BiPredicate<TableField, CodegenColumnDO> pr =
+        // 3.1 计算需要【修改】的字段,插入时重新插入,删除时将原来的删除
+        Map<String, CodegenColumnDO> codegenColumnDOMap = convertMap(codegenColumns, CodegenColumnDO::getColumnName);
+        BiPredicate<TableField, CodegenColumnDO> primaryKeyPredicate =
                 (tableField, codegenColumn) -> tableField.getMetaInfo().getJdbcType().name().equals(codegenColumn.getDataType())
                         && tableField.getMetaInfo().isNullable() == codegenColumn.getNullable()
                         && tableField.isKeyFlag() == codegenColumn.getPrimaryKey()
                         && tableField.getComment().equals(codegenColumn.getColumnComment());
-        Map<String, CodegenColumnDO> codegenColumnDOMap = CollectionUtils.convertMap(codegenColumns, CodegenColumnDO::getColumnName);
-        //需要修改的字段
         Set<String> modifyFieldNames = tableFields.stream()
                 .filter(tableField -> codegenColumnDOMap.get(tableField.getColumnName()) != null
-                        && !pr.test(tableField, codegenColumnDOMap.get(tableField.getColumnName())))
+                        && !primaryKeyPredicate.test(tableField, codegenColumnDOMap.get(tableField.getColumnName())))
                 .map(TableField::getColumnName)
                 .collect(Collectors.toSet());
-        // 计算需要删除的字段
-        Set<String> tableFieldNames = CollectionUtils.convertSet(tableFields, TableField::getName);
+        // 3.2 计算需要删除的字段
+        Set<String> tableFieldNames = convertSet(tableFields, TableField::getName);
         Set<Long> deleteColumnIds = codegenColumns.stream()
                 .filter(column -> (!tableFieldNames.contains(column.getColumnName())) || modifyFieldNames.contains(column.getColumnName()))
                 .map(CodegenColumnDO::getId).collect(Collectors.toSet());
@@ -193,10 +195,10 @@ public class CodegenServiceImpl implements CodegenService {
             throw exception(CODEGEN_SYNC_NONE_CHANGE);
         }
 
-        // 插入新增的字段
+        // 4.1 插入新增的字段
         List<CodegenColumnDO> columns = codegenBuilder.buildColumns(tableId, tableFields);
         codegenColumnMapper.insertBatch(columns);
-        // 删除不存在的字段
+        // 4.2 删除不存在的字段
         if (CollUtil.isNotEmpty(deleteColumnIds)) {
             codegenColumnMapper.deleteBatchIds(deleteColumnIds);
         }
@@ -227,7 +229,7 @@ public class CodegenServiceImpl implements CodegenService {
     }
 
     @Override
-    public CodegenTableDO getCodegenTablePage(Long id) {
+    public CodegenTableDO getCodegenTable(Long id) {
         return codegenTableMapper.selectById(id);
     }
 
@@ -277,10 +279,10 @@ public class CodegenServiceImpl implements CodegenService {
     public List<DatabaseTableRespVO> getDatabaseTableList(Long dataSourceConfigId, String name, String comment) {
         List<TableInfo> tables = databaseTableService.getTableList(dataSourceConfigId, name, comment);
         // 移除在 Codegen 中,已经存在的
-        Set<String> existsTables = CollectionUtils.convertSet(
+        Set<String> existsTables = convertSet(
                 codegenTableMapper.selectListByDataSourceConfigId(dataSourceConfigId), CodegenTableDO::getTableName);
         tables.removeIf(table -> existsTables.contains(table.getName()));
-        return CodegenConvert.INSTANCE.convertList04(tables);
+        return BeanUtils.toBean(tables, DatabaseTableRespVO.class);
     }
 
 }

+ 0 - 10
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/job/JobService.java

@@ -7,8 +7,6 @@ import cn.iocoder.yudao.module.infra.dal.dataobject.job.JobDO;
 import org.quartz.SchedulerException;
 
 import javax.validation.Valid;
-import java.util.Collection;
-import java.util.List;
 
 /**
  * 定时任务 Service 接口
@@ -62,14 +60,6 @@ public interface JobService {
      */
     JobDO getJob(Long id);
 
-    /**
-     * 获得定时任务列表
-     *
-     * @param ids 编号
-     * @return 定时任务列表
-     */
-    List<JobDO> getJobList(Collection<Long> ids);
-
     /**
      * 获得定时任务分页
      *

+ 0 - 5
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/job/JobServiceImpl.java

@@ -147,11 +147,6 @@ public class JobServiceImpl implements JobService {
         return jobMapper.selectById(id);
     }
 
-    @Override
-    public List<JobDO> getJobList(Collection<Long> ids) {
-        return jobMapper.selectBatchIds(ids);
-    }
-
     @Override
     public PageResult<JobDO> getJobPage(JobPageReqVO pageReqVO) {
 		return jobMapper.selectPage(pageReqVO);

+ 1 - 1
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm

@@ -269,7 +269,7 @@ const queryParams = reactive({
   #foreach ($column in $columns)
     #if ($column.listOperation)
       #if ($column.listOperationCondition != 'BETWEEN')
-  $column.javaField: null,
+  $column.javaField: undefined,
   #end
       #if ($column.htmlType == "datetime" || $column.listOperationCondition == "BETWEEN")
   $column.javaField: [],

+ 556 - 0
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenServiceImplTest.java

@@ -0,0 +1,556 @@
+package cn.iocoder.yudao.module.infra.service.codegen;
+
+import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenCreateListReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenUpdateReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.column.CodegenColumnSaveReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.CodegenTablePageReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.DatabaseTableRespVO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
+import cn.iocoder.yudao.module.infra.dal.mysql.codegen.CodegenColumnMapper;
+import cn.iocoder.yudao.module.infra.dal.mysql.codegen.CodegenTableMapper;
+import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum;
+import cn.iocoder.yudao.module.infra.enums.codegen.CodegenSceneEnum;
+import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
+import cn.iocoder.yudao.module.infra.framework.codegen.config.CodegenProperties;
+import cn.iocoder.yudao.module.infra.service.codegen.inner.CodegenBuilder;
+import cn.iocoder.yudao.module.infra.service.codegen.inner.CodegenEngine;
+import cn.iocoder.yudao.module.infra.service.db.DatabaseTableService;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import com.baomidou.mybatisplus.generator.config.po.TableField;
+import com.baomidou.mybatisplus.generator.config.po.TableInfo;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+
+import javax.annotation.Resource;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * {@link CodegenServiceImpl} 的单元测试类
+ *
+ * @author 芋道源码
+ */
+@Import(CodegenServiceImpl.class)
+public class CodegenServiceImplTest extends BaseDbUnitTest {
+
+    @Resource
+    private CodegenServiceImpl codegenService;
+
+    @Resource
+    private CodegenTableMapper codegenTableMapper;
+    @Resource
+    private CodegenColumnMapper codegenColumnMapper;
+
+    @MockBean
+    private DatabaseTableService databaseTableService;
+
+    @MockBean
+    private AdminUserApi userApi;
+
+    @MockBean
+    private CodegenBuilder codegenBuilder;
+    @MockBean
+    private CodegenEngine codegenEngine;
+
+    @MockBean
+    private CodegenProperties codegenProperties;
+
+    @Test
+    public void testCreateCodegenList() {
+        // 准备参数
+        Long userId = randomLongId();
+        CodegenCreateListReqVO reqVO = randomPojo(CodegenCreateListReqVO.class,
+                o -> o.setDataSourceConfigId(1L).setTableNames(Collections.singletonList("t_yunai")));
+        // mock 方法(TableInfo)
+        TableInfo tableInfo = mock(TableInfo.class);
+        when(databaseTableService.getTable(eq(1L), eq("t_yunai")))
+                .thenReturn(tableInfo);
+        when(tableInfo.getComment()).thenReturn("芋艿");
+        // mock 方法(TableInfo fields)
+        TableField field01 = mock(TableField.class);
+        when(field01.getComment()).thenReturn("主键");
+        TableField field02 = mock(TableField.class);
+        when(field02.getComment()).thenReturn("名字");
+        List<TableField> fields = Arrays.asList(field01, field02);
+        when(tableInfo.getFields()).thenReturn(fields);
+        // mock 方法(CodegenTableDO)
+        CodegenTableDO table = randomPojo(CodegenTableDO.class);
+        when(codegenBuilder.buildTable(same(tableInfo))).thenReturn(table);
+        // mock 方法(AdminUserRespDTO)
+        AdminUserRespDTO user = randomPojo(AdminUserRespDTO.class, o -> o.setNickname("芋头"));
+        when(userApi.getUser(eq(userId))).thenReturn(user);
+        // mock 方法(CodegenColumnDO)
+        List<CodegenColumnDO> columns = randomPojoList(CodegenColumnDO.class);
+        when(codegenBuilder.buildColumns(eq(table.getId()), same(fields)))
+                .thenReturn(columns);
+        // mock 方法(CodegenProperties)
+        when(codegenProperties.getFrontType()).thenReturn(CodegenFrontTypeEnum.VUE3.getType());
+
+        // 调用
+        List<Long> result = codegenService.createCodegenList(userId, reqVO);
+        // 断言
+        assertEquals(1, result.size());
+        // 断言(CodegenTableDO)
+        CodegenTableDO dbTable = codegenTableMapper.selectList().get(0);
+        assertPojoEquals(table, dbTable);
+        assertEquals(1L, dbTable.getDataSourceConfigId());
+        assertEquals(CodegenSceneEnum.ADMIN.getScene(), dbTable.getScene());
+        assertEquals(CodegenFrontTypeEnum.VUE3.getType(), dbTable.getFrontType());
+        assertEquals("芋头", dbTable.getAuthor());
+        // 断言(CodegenColumnDO)
+        List<CodegenColumnDO> dbColumns = codegenColumnMapper.selectList();
+        assertEquals(columns.size(), dbColumns.size());
+        assertTrue(dbColumns.get(0).getPrimaryKey());
+        for (int i = 0; i < dbColumns.size(); i++) {
+            assertPojoEquals(columns.get(i), dbColumns.get(i));
+        }
+    }
+
+    @Test
+    public void testValidateTableInfo() {
+        // 情况一
+        assertServiceException(() -> codegenService.validateTableInfo(null),
+                CODEGEN_IMPORT_TABLE_NULL);
+        // 情况二
+        TableInfo tableInfo = mock(TableInfo.class);
+        assertServiceException(() -> codegenService.validateTableInfo(tableInfo),
+                CODEGEN_TABLE_INFO_TABLE_COMMENT_IS_NULL);
+        // 情况三
+        when(tableInfo.getComment()).thenReturn("芋艿");
+        assertServiceException(() -> codegenService.validateTableInfo(tableInfo),
+                CODEGEN_IMPORT_COLUMNS_NULL);
+        // 情况四
+        TableField field = mock(TableField.class);
+        when(field.getName()).thenReturn("name");
+        when(tableInfo.getFields()).thenReturn(Collections.singletonList(field));
+        assertServiceException(() -> codegenService.validateTableInfo(tableInfo),
+                CODEGEN_TABLE_INFO_COLUMN_COMMENT_IS_NULL, field.getName());
+    }
+
+    @Test
+    public void testUpdateCodegen_notExists() {
+        // 准备参数
+        CodegenUpdateReqVO updateReqVO = randomPojo(CodegenUpdateReqVO.class);
+        // mock 方法
+
+        // 调用,并断言
+        assertServiceException(() -> codegenService.updateCodegen(updateReqVO),
+                CODEGEN_TABLE_NOT_EXISTS);
+    }
+
+    @Test
+    public void testUpdateCodegen_sub_masterNotExists() {
+        // mock 数据
+        CodegenTableDO table = randomPojo(CodegenTableDO.class,
+                o -> o.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
+                        .setScene(CodegenSceneEnum.ADMIN.getScene()));
+        codegenTableMapper.insert(table);
+        // 准备参数
+        CodegenUpdateReqVO updateReqVO = randomPojo(CodegenUpdateReqVO.class,
+                o -> o.getTable().setId(table.getId())
+                        .setTemplateType(CodegenTemplateTypeEnum.SUB.getType()));
+
+        // 调用,并断言
+        assertServiceException(() -> codegenService.updateCodegen(updateReqVO),
+                CODEGEN_MASTER_TABLE_NOT_EXISTS, updateReqVO.getTable().getMasterTableId());
+    }
+
+    @Test
+    public void testUpdateCodegen_sub_columnNotExists() {
+        // mock 数据
+        CodegenTableDO subTable = randomPojo(CodegenTableDO.class,
+                o -> o.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
+                        .setScene(CodegenSceneEnum.ADMIN.getScene()));
+        codegenTableMapper.insert(subTable);
+        // mock 数据(master)
+        CodegenTableDO masterTable = randomPojo(CodegenTableDO.class,
+                o -> o.setTemplateType(CodegenTemplateTypeEnum.MASTER_ERP.getType())
+                        .setScene(CodegenSceneEnum.ADMIN.getScene()));
+        codegenTableMapper.insert(masterTable);
+        // 准备参数
+        CodegenUpdateReqVO updateReqVO = randomPojo(CodegenUpdateReqVO.class,
+                o -> o.getTable().setId(subTable.getId())
+                        .setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
+                        .setMasterTableId(masterTable.getId()));
+
+        // 调用,并断言
+        assertServiceException(() -> codegenService.updateCodegen(updateReqVO),
+                CODEGEN_SUB_COLUMN_NOT_EXISTS, updateReqVO.getTable().getSubJoinColumnId());
+    }
+
+    @Test
+    public void testUpdateCodegen_success() {
+        // mock 数据
+        CodegenTableDO table = randomPojo(CodegenTableDO.class,
+                o -> o.setTemplateType(CodegenTemplateTypeEnum.ONE.getType())
+                        .setScene(CodegenSceneEnum.ADMIN.getScene()));
+        codegenTableMapper.insert(table);
+        CodegenColumnDO column01 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
+        codegenColumnMapper.insert(column01);
+        CodegenColumnDO column02 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
+        codegenColumnMapper.insert(column02);
+        // 准备参数
+        CodegenUpdateReqVO updateReqVO = randomPojo(CodegenUpdateReqVO.class,
+                o -> o.getTable().setId(table.getId())
+                        .setTemplateType(CodegenTemplateTypeEnum.ONE.getType())
+                        .setScene(CodegenSceneEnum.ADMIN.getScene()));
+        CodegenColumnSaveReqVO columnVO01 = randomPojo(CodegenColumnSaveReqVO.class,
+                o -> o.setId(column01.getId()).setTableId(table.getId()));
+        CodegenColumnSaveReqVO columnVO02 = randomPojo(CodegenColumnSaveReqVO.class,
+                o -> o.setId(column02.getId()).setTableId(table.getId()));
+        updateReqVO.setColumns(Arrays.asList(columnVO01, columnVO02));
+
+        // 调用
+        codegenService.updateCodegen(updateReqVO);
+        // 断言
+        CodegenTableDO dbTable = codegenTableMapper.selectById(table.getId());
+        assertPojoEquals(updateReqVO.getTable(), dbTable);
+        List<CodegenColumnDO> dbColumns = codegenColumnMapper.selectList();
+        assertEquals(2, dbColumns.size());
+        assertPojoEquals(columnVO01, dbColumns.get(0));
+        assertPojoEquals(columnVO02, dbColumns.get(1));
+    }
+
+    @Test
+    public void testSyncCodegenFromDB() {
+        // mock 数据(CodegenTableDO)
+        CodegenTableDO table = randomPojo(CodegenTableDO.class, o -> o.setTableName("t_yunai")
+                .setDataSourceConfigId(1L).setScene(CodegenSceneEnum.ADMIN.getScene()));
+        codegenTableMapper.insert(table);
+        CodegenColumnDO column01 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId())
+                .setColumnName("id"));
+        codegenColumnMapper.insert(column01);
+        CodegenColumnDO column02 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId())
+                .setColumnName("name"));
+        codegenColumnMapper.insert(column02);
+        // 准备参数
+        Long tableId = table.getId();
+        // mock 方法(TableInfo)
+        TableInfo tableInfo = mock(TableInfo.class);
+        when(databaseTableService.getTable(eq(1L), eq("t_yunai")))
+                .thenReturn(tableInfo);
+        when(tableInfo.getComment()).thenReturn("芋艿");
+        // mock 方法(TableInfo fields)
+        TableField field01 = mock(TableField.class);
+        when(field01.getComment()).thenReturn("主键");
+        TableField field03 = mock(TableField.class);
+        when(field03.getComment()).thenReturn("分类");
+        List<TableField> fields = Arrays.asList(field01, field03);
+        when(tableInfo.getFields()).thenReturn(fields);
+        when(databaseTableService.getTable(eq(1L), eq("t_yunai")))
+                .thenReturn(tableInfo);
+        // mock 方法(CodegenTableDO)
+        List<CodegenColumnDO> newColumns = randomPojoList(CodegenColumnDO.class);
+        when(codegenBuilder.buildColumns(eq(table.getId()), argThat(tableFields -> {
+            assertEquals(2, tableFields.size());
+            assertSame(tableInfo.getFields(), tableFields);
+            return true;
+        }))).thenReturn(newColumns);
+
+        // 调用
+        codegenService.syncCodegenFromDB(tableId);
+        // 断言
+        List<CodegenColumnDO> dbColumns = codegenColumnMapper.selectList();
+        assertEquals(newColumns.size(), dbColumns.size());
+        assertPojoEquals(newColumns.get(0), dbColumns.get(0));
+        assertPojoEquals(newColumns.get(1), dbColumns.get(1));
+    }
+
+    @Test
+    public void testDeleteCodegen_notExists() {
+        assertServiceException(() -> codegenService.deleteCodegen(randomLongId()),
+                CODEGEN_TABLE_NOT_EXISTS);
+    }
+
+    @Test
+    public void testDeleteCodegen_success() {
+        // mock 数据
+        CodegenTableDO table = randomPojo(CodegenTableDO.class,
+                o -> o.setScene(CodegenSceneEnum.ADMIN.getScene()));
+        codegenTableMapper.insert(table);
+        CodegenColumnDO column = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
+        codegenColumnMapper.insert(column);
+        // 准备参数
+        Long tableId = table.getId();
+
+        // 调用
+        codegenService.deleteCodegen(tableId);
+        // 断言
+        assertNull(codegenTableMapper.selectById(tableId));
+        assertEquals(0, codegenColumnMapper.selectList().size());
+    }
+
+    @Test
+    public void testGetCodegenTableList() {
+        // mock 数据
+        CodegenTableDO table01 = randomPojo(CodegenTableDO.class,
+                o -> o.setScene(CodegenSceneEnum.ADMIN.getScene()));
+        codegenTableMapper.insert(table01);
+        CodegenTableDO table02 = randomPojo(CodegenTableDO.class,
+                o -> o.setScene(CodegenSceneEnum.ADMIN.getScene()));
+        codegenTableMapper.insert(table02);
+        // 准备参数
+        Long dataSourceConfigId = table01.getDataSourceConfigId();
+
+        // 调用
+        List<CodegenTableDO> result = codegenService.getCodegenTableList(dataSourceConfigId);
+        // 断言
+        assertEquals(1, result.size());
+        assertPojoEquals(table01, result.get(0));
+    }
+
+    @Test
+    public void testGetCodegenTablePage() {
+        // mock 数据
+        CodegenTableDO tableDO = randomPojo(CodegenTableDO.class, o -> {
+            o.setTableName("t_yunai");
+            o.setTableComment("芋艿");
+            o.setClassName("SystemYunai");
+            o.setCreateTime(buildTime(2021, 3, 10));
+        }).setScene(CodegenSceneEnum.ADMIN.getScene());
+        codegenTableMapper.insert(tableDO);
+        // 测试 tableName 不匹配
+        codegenTableMapper.insert(cloneIgnoreId(tableDO, o -> o.setTableName(randomString())));
+        // 测试 tableComment 不匹配
+        codegenTableMapper.insert(cloneIgnoreId(tableDO, o -> o.setTableComment(randomString())));
+        // 测试 className 不匹配
+        codegenTableMapper.insert(cloneIgnoreId(tableDO, o -> o.setClassName(randomString())));
+        // 测试 createTime 不匹配
+        codegenTableMapper.insert(cloneIgnoreId(tableDO, logDO -> logDO.setCreateTime(buildTime(2021, 4, 10))));
+        // 准备参数
+        CodegenTablePageReqVO reqVO = new CodegenTablePageReqVO();
+        reqVO.setTableName("yunai");
+        reqVO.setTableComment("芋");
+        reqVO.setClassName("Yunai");
+        reqVO.setCreateTime(buildBetweenTime(2021, 3, 1, 2021, 3, 31));
+
+        // 调用
+        PageResult<CodegenTableDO> pageResult = codegenService.getCodegenTablePage(reqVO);
+        // 断言,只查到了一条符合条件的
+        assertEquals(1, pageResult.getTotal());
+        assertEquals(1, pageResult.getList().size());
+        assertPojoEquals(tableDO, pageResult.getList().get(0));
+    }
+
+    @Test
+    public void testGetCodegenTable() {
+        // mock 数据
+        CodegenTableDO tableDO = randomPojo(CodegenTableDO.class, o -> o.setScene(CodegenSceneEnum.ADMIN.getScene()));
+        codegenTableMapper.insert(tableDO);
+        // 准备参数
+        Long id = tableDO.getId();
+
+        // 调用
+        CodegenTableDO result = codegenService.getCodegenTable(id);
+        // 断言
+        assertPojoEquals(tableDO, result);
+    }
+
+    @Test
+    public void testGetCodegenColumnListByTableId() {
+        // mock 数据
+        CodegenColumnDO column01 = randomPojo(CodegenColumnDO.class);
+        codegenColumnMapper.insert(column01);
+        CodegenColumnDO column02 = randomPojo(CodegenColumnDO.class);
+        codegenColumnMapper.insert(column02);
+        // 准备参数
+        Long tableId = column01.getTableId();
+
+        // 调用
+        List<CodegenColumnDO> result = codegenService.getCodegenColumnListByTableId(tableId);
+        // 断言
+        assertEquals(1, result.size());
+        assertPojoEquals(column01, result.get(0));
+    }
+
+    @Test
+    public void testGenerationCodes_tableNotExists() {
+        assertServiceException(() -> codegenService.generationCodes(randomLongId()),
+                CODEGEN_TABLE_NOT_EXISTS);
+    }
+
+    @Test
+    public void testGenerationCodes_columnNotExists() {
+        // mock 数据(CodegenTableDO)
+        CodegenTableDO table = randomPojo(CodegenTableDO.class,
+                o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
+                        .setTemplateType(CodegenTemplateTypeEnum.MASTER_NORMAL.getType()));
+        codegenTableMapper.insert(table);
+        // 准备参数
+        Long tableId = table.getId();
+
+        // 调用,并断言
+        assertServiceException(() -> codegenService.generationCodes(tableId),
+                CODEGEN_COLUMN_NOT_EXISTS);
+    }
+
+    @Test
+    public void testGenerationCodes_sub_tableNotExists() {
+        // mock 数据(CodegenTableDO)
+        CodegenTableDO table = randomPojo(CodegenTableDO.class,
+                o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
+                        .setTemplateType(CodegenTemplateTypeEnum.MASTER_NORMAL.getType()));
+        codegenTableMapper.insert(table);
+        // mock 数据(CodegenColumnDO)
+        CodegenColumnDO column01 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
+        codegenColumnMapper.insert(column01);
+        // 准备参数
+        Long tableId = table.getId();
+
+        // 调用,并断言
+        assertServiceException(() -> codegenService.generationCodes(tableId),
+                CODEGEN_MASTER_GENERATION_FAIL_NO_SUB_TABLE);
+    }
+
+    @Test
+    public void testGenerationCodes_sub_columnNotExists() {
+        // mock 数据(CodegenTableDO)
+        CodegenTableDO table = randomPojo(CodegenTableDO.class,
+                o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
+                        .setTemplateType(CodegenTemplateTypeEnum.MASTER_NORMAL.getType()));
+        codegenTableMapper.insert(table);
+        // mock 数据(CodegenColumnDO)
+        CodegenColumnDO column01 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
+        codegenColumnMapper.insert(column01);
+        // mock 数据(sub CodegenTableDO)
+        CodegenTableDO subTable = randomPojo(CodegenTableDO.class,
+                o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
+                        .setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
+                        .setMasterTableId(table.getId()));
+        codegenTableMapper.insert(subTable);
+        // 准备参数
+        Long tableId = table.getId();
+
+        // 调用,并断言
+        assertServiceException(() -> codegenService.generationCodes(tableId),
+                CODEGEN_SUB_COLUMN_NOT_EXISTS, subTable.getId());
+    }
+
+    @Test
+    public void testGenerationCodes_one_success() {
+        // mock 数据(CodegenTableDO)
+        CodegenTableDO table = randomPojo(CodegenTableDO.class,
+                o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
+                        .setTemplateType(CodegenTemplateTypeEnum.ONE.getType()));
+        codegenTableMapper.insert(table);
+        // mock 数据(CodegenColumnDO)
+        CodegenColumnDO column01 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
+        codegenColumnMapper.insert(column01);
+        CodegenColumnDO column02 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
+        codegenColumnMapper.insert(column02);
+        // mock 执行生成
+        Map<String, String> codes = MapUtil.of(randomString(), randomString());
+        when(codegenEngine.execute(eq(table), argThat(columns -> {
+            assertEquals(2, columns.size());
+            assertEquals(column01, columns.get(0));
+            assertEquals(column02, columns.get(1));
+            return true;
+        }), isNull(), isNull())).thenReturn(codes);
+        // 准备参数
+        Long tableId = table.getId();
+
+        // 调用
+        Map<String, String> result = codegenService.generationCodes(tableId);
+        // 断言
+        assertSame(codes, result);
+    }
+
+    @Test
+    public void testGenerationCodes_master_success() {
+        // mock 数据(CodegenTableDO)
+        CodegenTableDO table = randomPojo(CodegenTableDO.class,
+                o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
+                        .setTemplateType(CodegenTemplateTypeEnum.MASTER_NORMAL.getType()));
+        codegenTableMapper.insert(table);
+        // mock 数据(CodegenColumnDO)
+        CodegenColumnDO column01 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
+        codegenColumnMapper.insert(column01);
+        CodegenColumnDO column02 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
+        codegenColumnMapper.insert(column02);
+        // mock 数据(sub CodegenTableDO)
+        CodegenTableDO subTable = randomPojo(CodegenTableDO.class,
+                o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
+                        .setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
+                        .setMasterTableId(table.getId())
+                        .setSubJoinColumnId(1024L));
+        codegenTableMapper.insert(subTable);
+        // mock 数据(sub CodegenColumnDO)
+        CodegenColumnDO subColumn01 = randomPojo(CodegenColumnDO.class, o -> o.setId(1024L).setTableId(subTable.getId()));
+        codegenColumnMapper.insert(subColumn01);
+        // mock 执行生成
+        Map<String, String> codes = MapUtil.of(randomString(), randomString());
+        when(codegenEngine.execute(eq(table), argThat(columns -> {
+            assertEquals(2, columns.size());
+            assertEquals(column01, columns.get(0));
+            assertEquals(column02, columns.get(1));
+            return true;
+        }), argThat(tables -> {
+            assertEquals(1, tables.size());
+            assertPojoEquals(subTable, tables.get(0));
+            return true;
+        }), argThat(columns -> {
+            assertEquals(1, columns.size());
+            assertPojoEquals(subColumn01, columns.size());
+            return true;
+        }))).thenReturn(codes);
+        // 准备参数
+        Long tableId = table.getId();
+
+        // 调用
+        Map<String, String> result = codegenService.generationCodes(tableId);
+        // 断言
+        assertSame(codes, result);
+    }
+
+    @Test
+    public void testGetDatabaseTableList() {
+        // 准备参数
+        Long dataSourceConfigId = randomLongId();
+        String name = randomString();
+        String comment = randomString();
+        // mock 方法
+        TableInfo tableInfo01 = mock(TableInfo.class);
+        when(tableInfo01.getName()).thenReturn("t_yunai");
+        when(tableInfo01.getComment()).thenReturn("芋艿");
+        TableInfo tableInfo02 = mock(TableInfo.class);
+        when(tableInfo02.getName()).thenReturn("t_yunai_02");
+        when(tableInfo02.getComment()).thenReturn("芋艿_02");
+        when(databaseTableService.getTableList(eq(dataSourceConfigId), eq(name), eq(comment)))
+                .thenReturn(ListUtil.toList(tableInfo01, tableInfo02));
+        // mock 数据
+        CodegenTableDO tableDO = randomPojo(CodegenTableDO.class,
+                o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
+                        .setTableName("t_yunai_02")
+                        .setDataSourceConfigId(dataSourceConfigId));
+        codegenTableMapper.insert(tableDO);
+
+        // 调用
+        List<DatabaseTableRespVO> result = codegenService.getDatabaseTableList(dataSourceConfigId, name, comment);
+        // 断言
+        assertEquals(1, result.size());
+        assertEquals("t_yunai", result.get(0).getName());
+        assertEquals("芋艿", result.get(0).getComment());
+    }
+
+}

+ 89 - 0
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenBuilderTest.java

@@ -0,0 +1,89 @@
+package cn.iocoder.yudao.module.infra.service.codegen.inner;
+
+import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
+import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
+import com.baomidou.mybatisplus.generator.config.po.TableField;
+import com.baomidou.mybatisplus.generator.config.po.TableInfo;
+import com.baomidou.mybatisplus.generator.config.rules.IColumnType;
+import org.apache.ibatis.type.JdbcType;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+
+import java.util.Collections;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class CodegenBuilderTest extends BaseMockitoUnitTest {
+
+    @InjectMocks
+    private CodegenBuilder codegenBuilder;
+
+    @Test
+    public void testBuildTable() {
+        // 准备参数
+        TableInfo tableInfo = mock(TableInfo.class);
+        // mock 方法
+        when(tableInfo.getName()).thenReturn("system_user");
+        when(tableInfo.getComment()).thenReturn("用户");
+
+        // 调用
+        CodegenTableDO table = codegenBuilder.buildTable(tableInfo);
+        // 断言
+        assertEquals("system_user", table.getTableName());
+        assertEquals("用户", table.getTableComment());
+        assertEquals("system", table.getModuleName());
+        assertEquals("user", table.getBusinessName());
+        assertEquals("User", table.getClassName());
+        assertEquals("用户", table.getClassComment());
+    }
+
+    @Test
+    public void testBuildColumns() {
+        // 准备参数
+        Long tableId = randomLongId();
+        TableField tableField = mock(TableField.class);
+        List<TableField> tableFields = Collections.singletonList(tableField);
+        // mock 方法
+        TableField.MetaInfo metaInfo = mock(TableField.MetaInfo.class);
+        when(tableField.getMetaInfo()).thenReturn(metaInfo);
+        when(metaInfo.getJdbcType()).thenReturn(JdbcType.BIGINT);
+        when(tableField.getComment()).thenReturn("编号");
+        when(tableField.isKeyFlag()).thenReturn(true);
+        when(tableField.isKeyIdentityFlag()).thenReturn(true);
+        IColumnType columnType = mock(IColumnType.class);
+        when(tableField.getColumnType()).thenReturn(columnType);
+        when(columnType.getType()).thenReturn("Long");
+        when(tableField.getName()).thenReturn("id2");
+        when(tableField.getPropertyName()).thenReturn("id");
+
+        // 调用
+        List<CodegenColumnDO> columns = codegenBuilder.buildColumns(tableId, tableFields);
+        // 断言
+        assertEquals(1, columns.size());
+        CodegenColumnDO column = columns.get(0);
+        assertEquals(tableId, column.getTableId());
+        assertEquals("id2", column.getColumnName());
+        assertEquals("BIGINT", column.getDataType());
+        assertEquals("编号", column.getColumnComment());
+        assertFalse(column.getNullable());
+        assertTrue(column.getPrimaryKey());
+        assertTrue(column.getAutoIncrement());
+        assertEquals(1, column.getOrdinalPosition());
+        assertEquals("Long", column.getJavaType());
+        assertEquals("id", column.getJavaField());
+        assertNull(column.getDictType());
+        assertNotNull(column.getExample());
+        assertFalse(column.getCreateOperation());
+        assertTrue(column.getUpdateOperation());
+        assertFalse(column.getListOperation());
+        assertEquals("=", column.getListOperationCondition());
+        assertTrue(column.getListOperationResult());
+        assertEquals("input", column.getHtmlType());
+    }
+
+}

+ 0 - 4
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/package-info.java

@@ -1,4 +0,0 @@
-/**
- * 占位,无其它作用
- */
-package cn.iocoder.yudao.module.infra.service.codegen;

+ 25 - 0
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/job/JobLogServiceImplTest.java

@@ -10,8 +10,11 @@ import org.junit.jupiter.api.Test;
 import org.springframework.context.annotation.Import;
 
 import javax.annotation.Resource;
+import java.time.Duration;
 import java.time.LocalDateTime;
+import java.util.List;
 
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.addTime;
 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
@@ -92,6 +95,28 @@ public class JobLogServiceImplTest extends BaseDbUnitTest {
         assertEquals(result, dbLog.getResult());
     }
 
+    @Test
+    public void testCleanJobLog() {
+        // mock 数据
+        JobLogDO log01 = randomPojo(JobLogDO.class, o -> o.setCreateTime(addTime(Duration.ofDays(-3))))
+                .setExecuteIndex(1);
+        jobLogMapper.insert(log01);
+        JobLogDO log02 = randomPojo(JobLogDO.class, o -> o.setCreateTime(addTime(Duration.ofDays(-1))))
+                .setExecuteIndex(1);
+        jobLogMapper.insert(log02);
+        // 准备参数
+        Integer exceedDay = 2;
+        Integer deleteLimit = 1;
+
+        // 调用
+        Integer count = jobLogService.cleanJobLog(exceedDay, deleteLimit);
+        // 断言
+        assertEquals(1, count);
+        List<JobLogDO> logs = jobLogMapper.selectList();
+        assertEquals(1, logs.size());
+        assertEquals(log02, logs.get(0));
+    }
+
     @Test
     public void testGetJobLog() {
         // mock 数据

+ 23 - 2
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/logger/ApiAccessLogServiceImplTest.java

@@ -12,9 +12,10 @@ import org.junit.jupiter.api.Test;
 import org.springframework.context.annotation.Import;
 
 import javax.annotation.Resource;
+import java.time.Duration;
+import java.util.List;
 
-import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
-import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
@@ -73,6 +74,26 @@ public class ApiAccessLogServiceImplTest extends BaseDbUnitTest {
         assertPojoEquals(apiAccessLogDO, pageResult.getList().get(0));
     }
 
+    @Test
+    public void testCleanJobLog() {
+        // mock 数据
+        ApiAccessLogDO log01 = randomPojo(ApiAccessLogDO.class, o -> o.setCreateTime(addTime(Duration.ofDays(-3))));
+        apiAccessLogMapper.insert(log01);
+        ApiAccessLogDO log02 = randomPojo(ApiAccessLogDO.class, o -> o.setCreateTime(addTime(Duration.ofDays(-1))));
+        apiAccessLogMapper.insert(log02);
+        // 准备参数
+        Integer exceedDay = 2;
+        Integer deleteLimit = 1;
+
+        // 调用
+        Integer count = apiAccessLogService.cleanAccessLog(exceedDay, deleteLimit);
+        // 断言
+        assertEquals(1, count);
+        List<ApiAccessLogDO> logs = apiAccessLogMapper.selectList();
+        assertEquals(1, logs.size());
+        assertEquals(log02, logs.get(0));
+    }
+
     @Test
     public void testCreateApiAccessLog() {
         // 准备参数

+ 27 - 6
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/logger/ApiErrorLogServiceImplTest.java

@@ -12,10 +12,11 @@ import org.junit.jupiter.api.Test;
 import org.springframework.context.annotation.Import;
 
 import javax.annotation.Resource;
+import java.time.Duration;
+import java.util.List;
 
 import static cn.hutool.core.util.RandomUtil.randomEle;
-import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
-import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
@@ -50,13 +51,13 @@ public class ApiErrorLogServiceImplTest extends BaseDbUnitTest {
         // 测试 userId 不匹配
         apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, o -> o.setUserId(3344L)));
         // 测试 userType 不匹配
-        apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, logDO -> logDO.setUserType(UserTypeEnum.MEMBER.getValue())));
+        apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, o -> o.setUserType(UserTypeEnum.MEMBER.getValue())));
         // 测试 applicationName 不匹配
-        apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, logDO -> logDO.setApplicationName("test")));
+        apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, o -> o.setApplicationName("test")));
         // 测试 requestUrl 不匹配
-        apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, logDO -> logDO.setRequestUrl("bar")));
+        apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, o -> o.setRequestUrl("bar")));
         // 测试 exceptionTime 不匹配:构造一个早期时间 2021-02-06 00:00:00
-        apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, logDO -> logDO.setExceptionTime(buildTime(2021, 2, 6))));
+        apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, o -> o.setExceptionTime(buildTime(2021, 2, 6))));
         // 测试 progressStatus 不匹配
         apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, logDO -> logDO.setProcessStatus(ApiErrorLogProcessStatusEnum.DONE.getStatus())));
         // 准备参数
@@ -139,4 +140,24 @@ public class ApiErrorLogServiceImplTest extends BaseDbUnitTest {
                 API_ERROR_LOG_NOT_FOUND);
     }
 
+    @Test
+    public void testCleanJobLog() {
+        // mock 数据
+        ApiErrorLogDO log01 = randomPojo(ApiErrorLogDO.class, o -> o.setCreateTime(addTime(Duration.ofDays(-3))));
+        apiErrorLogMapper.insert(log01);
+        ApiErrorLogDO log02 = randomPojo(ApiErrorLogDO.class, o -> o.setCreateTime(addTime(Duration.ofDays(-1))));
+        apiErrorLogMapper.insert(log02);
+        // 准备参数
+        Integer exceedDay = 2;
+        Integer deleteLimit = 1;
+
+        // 调用
+        Integer count = apiErrorLogService.cleanErrorLog(exceedDay, deleteLimit);
+        // 断言
+        assertEquals(1, count);
+        List<ApiErrorLogDO> logs = apiErrorLogMapper.selectList();
+        assertEquals(1, logs.size());
+        assertEquals(log02, logs.get(0));
+    }
+
 }

+ 4 - 4
yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_master_erp/vue/index

@@ -197,11 +197,11 @@ const total = ref(0) // 列表的总页数
 const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
-  name: null,
-  birthday: null,
+  name: undefined,
+  birthday: undefined,
   birthday: [],
-  sex: null,
-  enabled: null,
+  sex: undefined,
+  enabled: undefined,
   createTime: []
 })
 const queryFormRef = ref() // 搜索的表单

+ 4 - 4
yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_master_inner/vue/index

@@ -192,11 +192,11 @@ const total = ref(0) // 列表的总页数
 const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
-  name: null,
-  birthday: null,
+  name: undefined,
+  birthday: undefined,
   birthday: [],
-  sex: null,
-  enabled: null,
+  sex: undefined,
+  enabled: undefined,
   createTime: []
 })
 const queryFormRef = ref() // 搜索的表单

+ 4 - 4
yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_master_normal/vue/index

@@ -177,11 +177,11 @@ const total = ref(0) // 列表的总页数
 const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
-  name: null,
-  birthday: null,
+  name: undefined,
+  birthday: undefined,
   birthday: [],
-  sex: null,
-  enabled: null,
+  sex: undefined,
+  enabled: undefined,
   createTime: []
 })
 const queryFormRef = ref() // 搜索的表单

+ 4 - 4
yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_one/vue/index

@@ -177,11 +177,11 @@ const total = ref(0) // 列表的总页数
 const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
-  name: null,
-  birthday: null,
+  name: undefined,
+  birthday: undefined,
   birthday: [],
-  sex: null,
-  enabled: null,
+  sex: undefined,
+  enabled: undefined,
   createTime: []
 })
 const queryFormRef = ref() // 搜索的表单

+ 1 - 1
yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_tree/vue/index

@@ -106,7 +106,7 @@ const { t } = useI18n() // 国际化
 const loading = ref(true) // 列表的加载中
 const list = ref([]) // 列表的数据
 const queryParams = reactive({
-  name: null
+  name: undefined
 })
 const queryFormRef = ref() // 搜索的表单
 const exportLoading = ref(false) // 导出的加载中

+ 2 - 0
yudao-module-infra/yudao-module-infra-biz/src/test/resources/sql/clean.sql

@@ -8,3 +8,5 @@ DELETE FROM "infra_api_error_log";
 DELETE FROM "infra_file_config";
 DELETE FROM "infra_test_demo";
 DELETE FROM "infra_data_source_config";
+DELETE FROM "infra_codegen_table";
+DELETE FROM "infra_codegen_column";

+ 56 - 0
yudao-module-infra/yudao-module-infra-biz/src/test/resources/sql/create_tables.sql

@@ -170,3 +170,59 @@ CREATE TABLE IF NOT EXISTS "infra_data_source_config" (
     "deleted" bit NOT NULL DEFAULT FALSE,
     PRIMARY KEY ("id")
 ) COMMENT '数据源配置表';
+
+CREATE TABLE IF NOT EXISTS "infra_codegen_table" (
+    "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+    "data_source_config_id" bigint not null,
+    "scene" tinyint not null DEFAULT 1,
+    "table_name" varchar(200) NOT NULL,
+    "table_comment" varchar(500) NOT NULL,
+    "remark" varchar(500) NOT NULL,
+    "module_name" varchar(30) NOT NULL,
+    "business_name" varchar(30) NOT NULL,
+    "class_name" varchar(100) NOT NULL,
+    "class_comment" varchar(50) NOT NULL,
+    "author" varchar(50) NOT NULL,
+    "template_type" tinyint not null DEFAULT 1,
+    "front_type" tinyint not null,
+    "parent_menu_id" bigint not null,
+    "master_table_id" bigint not null,
+    "sub_join_column_id" bigint not null,
+    "sub_join_many" bit not null,
+    "tree_parent_column_id" bigint not null,
+    "tree_name_column_id" bigint not null,
+    "creator" varchar(64) DEFAULT '',
+    "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "updater" varchar(64) DEFAULT '',
+    "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    "deleted" bit NOT NULL DEFAULT FALSE,
+    PRIMARY KEY ("id")
+) COMMENT '代码生成表定义表';
+
+CREATE TABLE IF NOT EXISTS "infra_codegen_column" (
+    "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+    "table_id" bigint not null,
+    "column_name" varchar(200) NOT NULL,
+    "data_type" varchar(100) NOT NULL,
+    "column_comment" varchar(500) NOT NULL,
+    "nullable" tinyint not null,
+    "primary_key" tinyint not null,
+    "auto_increment" varchar(5) not null,
+    "ordinal_position" int not null,
+    "java_type" varchar(32) NOT NULL,
+    "java_field" varchar(64) NOT NULL,
+    "dict_type" varchar(200) NOT NULL,
+    "example" varchar(64) NOT NULL,
+    "create_operation" bit not null,
+    "update_operation" bit not null,
+    "list_operation" bit not null,
+    "list_operation_condition" varchar(32) not null,
+    "list_operation_result" bit not null,
+    "html_type" varchar(32) NOT NULL,
+    "creator" varchar(64) DEFAULT '',
+    "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "updater" varchar(64) DEFAULT '',
+    "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    "deleted" bit NOT NULL DEFAULT FALSE,
+    PRIMARY KEY ("id")
+) COMMENT '代码生成表字段定义表';

+ 3 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dict/DictDataController.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.system.controller.admin.dict;
 
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
@@ -66,7 +67,8 @@ public class DictDataController {
     @Operation(summary = "获得全部字典数据列表", description = "一般用于管理后台缓存字典数据在本地")
     // 无需添加权限认证,因为前端全局都需要
     public CommonResult<List<DictDataSimpleRespVO>> getSimpleDictDataList() {
-        List<DictDataDO> list = dictDataService.getDictDataList();
+        List<DictDataDO> list = dictDataService.getDictDataList(
+                CommonStatusEnum.ENABLE.getStatus(), null);
         return success(BeanUtils.toBean(list, DictDataSimpleRespVO.class));
     }
 

+ 1 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/vo/user/SocialUserPageReqVO.java

@@ -23,7 +23,7 @@ public class SocialUserPageReqVO extends PageParam {
     @Schema(description = "用户昵称", example = "李四")
     private String nickname;
 
-    @Schema(description = "社交 openid", example = "oz-Jdt0kd_jdhUxJHQdBJMlOFN7w\n")
+    @Schema(description = "社交 openid", example = "oz-Jdt0kd_jdhUxJHQdBJMlOFN7w")
     private String openid;
 
     @Schema(description = "创建时间")

+ 1 - 2
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java

@@ -14,8 +14,7 @@ import java.util.Set;
 @Data
 public class UserSaveReqVO {
 
-    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
-    @NotNull(message = "用户编号不能为空")
+    @Schema(description = "用户编号", example = "1024")
     private Long id;
 
     @Schema(description = "用户账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao")

+ 3 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/app/dict/AppDictDataController.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.system.controller.app.dict;
 
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.system.controller.app.dict.vo.AppDictDataRespVO;
@@ -32,7 +33,8 @@ public class AppDictDataController {
     @Operation(summary = "根据字典类型查询字典数据信息")
     @Parameter(name = "type", description = "字典类型", required = true, example = "common_status")
     public CommonResult<List<AppDictDataRespVO>> getDictDataListByType(@RequestParam("type") String type) {
-        List<DictDataDO> list = dictDataService.getEnabledDictDataListByType(type);
+        List<DictDataDO> list = dictDataService.getDictDataList(
+                CommonStatusEnum.ENABLE.getStatus(), type);
         return success(BeanUtils.toBean(list, AppDictDataRespVO.class));
     }
 

+ 4 - 4
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/dict/DictDataMapper.java

@@ -40,10 +40,10 @@ public interface DictDataMapper extends BaseMapperX<DictDataDO> {
                 .orderByDesc(Arrays.asList(DictDataDO::getDictType, DictDataDO::getSort)));
     }
 
-    default List<DictDataDO> selectListByTypeAndStatus(String dictType, Integer status) {
-        return selectList(new LambdaQueryWrapper<DictDataDO>()
-                .eq(DictDataDO::getDictType, dictType)
-                .eq(DictDataDO::getStatus, status));
+    default List<DictDataDO> selectListByStatusAndDictType(Integer status, String dictType) {
+        return selectList(new LambdaQueryWrapperX<DictDataDO>()
+                .eqIfPresent(DictDataDO::getStatus, status)
+                .eqIfPresent(DictDataDO::getDictType, dictType));
     }
 
 }

+ 4 - 8
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/PostService.java

@@ -1,6 +1,5 @@
 package cn.iocoder.yudao.module.system.service.dept;
 
-import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.system.controller.admin.dept.vo.post.PostPageReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.dept.vo.post.PostSaveReqVO;
@@ -10,8 +9,6 @@ import org.springframework.lang.Nullable;
 import java.util.Collection;
 import java.util.List;
 
-import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
-
 /**
  * 岗位 Service 接口
  *
@@ -44,12 +41,10 @@ public interface PostService {
     /**
      * 获得岗位列表
      *
-     * @param ids 岗位编号数组。如果为空,不进行筛选
+     * @param ids 岗位编号数组
      * @return 部门列表
      */
-    default List<PostDO> getPostList(@Nullable Collection<Long> ids) {
-        return getPostList(ids, asSet(CommonStatusEnum.ENABLE.getStatus(), CommonStatusEnum.DISABLE.getStatus()));
-    }
+    List<PostDO> getPostList(@Nullable Collection<Long> ids);
 
     /**
      * 获得符合条件的岗位列表
@@ -58,7 +53,8 @@ public interface PostService {
      * @param statuses 状态数组。如果为空,不进行筛选
      * @return 部门列表
      */
-    List<PostDO> getPostList(@Nullable Collection<Long> ids, @Nullable Collection<Integer> statuses);
+    List<PostDO> getPostList(@Nullable Collection<Long> ids,
+                             @Nullable Collection<Integer> statuses);
 
     /**
      * 获得岗位分页列表

+ 9 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/PostServiceImpl.java

@@ -13,6 +13,7 @@ import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -107,6 +108,14 @@ public class PostServiceImpl implements PostService {
         }
     }
 
+    @Override
+    public List<PostDO> getPostList(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return Collections.emptyList();
+        }
+        return postMapper.selectBatchIds(ids);
+    }
+
     @Override
     public List<PostDO> getPostList(Collection<Long> ids, Collection<Integer> statuses) {
         return postMapper.selectList(ids, statuses);

+ 6 - 10
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictDataService.java

@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataPageReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataSaveReqVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
+import org.springframework.lang.Nullable;
 
 import java.util.Collection;
 import java.util.List;
@@ -40,9 +41,11 @@ public interface DictDataService {
     /**
      * 获得字典数据列表
      *
+     * @param status   状态
+     * @param dictType 字典类型
      * @return 字典数据全列表
      */
-    List<DictDataDO> getDictDataList();
+    List<DictDataDO> getDictDataList(@Nullable Integer status, @Nullable String dictType);
 
     /**
      * 获得字典数据分页列表
@@ -52,14 +55,6 @@ public interface DictDataService {
      */
     PageResult<DictDataDO> getDictDataPage(DictDataPageReqVO pageReqVO);
 
-    /**
-     * 获得字典数据列表
-     *
-     * @param dictType 字典类型
-     * @return 字典数据列表
-     */
-    List<DictDataDO> getEnabledDictDataListByType(String dictType);
-
     /**
      * 获得字典数据详情
      *
@@ -74,7 +69,7 @@ public interface DictDataService {
      * @param dictType 字典类型
      * @return 数据数量
      */
-    long countByDictType(String dictType);
+    long getDictDataCountByDictType(String dictType);
 
     /**
      * 校验字典数据们是否有效。如下情况,视为无效:
@@ -103,4 +98,5 @@ public interface DictDataService {
      * @return 字典数据
      */
     DictDataDO parseDictData(String dictType, String label);
+
 }

+ 3 - 10
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictDataServiceImpl.java

@@ -46,8 +46,8 @@ public class DictDataServiceImpl implements DictDataService {
     private DictDataMapper dictDataMapper;
 
     @Override
-    public List<DictDataDO> getDictDataList() {
-        List<DictDataDO> list = dictDataMapper.selectList(DictDataDO::getStatus, CommonStatusEnum.ENABLE.getStatus());
+    public List<DictDataDO> getDictDataList(Integer status, String dictType) {
+        List<DictDataDO> list = dictDataMapper.selectListByStatusAndDictType(status, dictType);
         list.sort(COMPARATOR_TYPE_AND_SORT);
         return list;
     }
@@ -57,13 +57,6 @@ public class DictDataServiceImpl implements DictDataService {
         return dictDataMapper.selectPage(pageReqVO);
     }
 
-    @Override
-    public List<DictDataDO> getEnabledDictDataListByType(String dictType) {
-        List<DictDataDO> list = dictDataMapper.selectListByTypeAndStatus(dictType, CommonStatusEnum.ENABLE.getStatus());
-        list.sort(COMPARATOR_TYPE_AND_SORT);
-        return list;
-    }
-
     @Override
     public DictDataDO getDictData(Long id) {
         return dictDataMapper.selectById(id);
@@ -106,7 +99,7 @@ public class DictDataServiceImpl implements DictDataService {
     }
 
     @Override
-    public long countByDictType(String dictType) {
+    public long getDictDataCountByDictType(String dictType) {
         return dictDataMapper.selectCountByDictType(dictType);
     }
 

+ 1 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictTypeServiceImpl.java

@@ -80,7 +80,7 @@ public class DictTypeServiceImpl implements DictTypeService {
         // 校验是否存在
         DictTypeDO dictType = validateDictTypeExists(id);
         // 校验是否有字典数据
-        if (dictDataService.countByDictType(dictType.getType()) > 0) {
+        if (dictDataService.getDictDataCountByDictType(dictType.getType()) > 0) {
             throw exception(DICT_TYPE_HAS_CHILDREN);
         }
         // 删除字典类型

+ 1 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/notify/NotifySendService.java

@@ -48,7 +48,7 @@ public interface NotifySendService {
                            String templateCode, Map<String, Object> templateParams);
 
     default void sendBatchNotify(List<String> mobiles, List<Long> userIds, Integer userType,
-                              String templateCode, Map<String, Object> templateParams) {
+                                 String templateCode, Map<String, Object> templateParams) {
         throw new UnsupportedOperationException("暂时不支持该操作,感兴趣可以实现该功能哟!");
     }
 

+ 2 - 2
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImpl.java

@@ -6,6 +6,7 @@ import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.extra.spring.SpringUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RolePageReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleSaveReqVO;
@@ -27,7 +28,6 @@ import javax.annotation.Resource;
 import java.util.*;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
 
@@ -198,7 +198,7 @@ public class RoleServiceImpl implements RoleService {
         }
         // 这里采用 for 循环从缓存中获取,主要考虑 Spring CacheManager 无法批量操作的问题
         RoleServiceImpl self = getSelf();
-        return convertList(ids, self::getRoleFromCache);
+        return CollectionUtils.convertList(ids, self::getRoleFromCache);
     }
 
     @Override

+ 0 - 49
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/bo/RoleCreateReqBO.java

@@ -1,49 +0,0 @@
-package cn.iocoder.yudao.module.system.service.permission.bo;
-
-import lombok.Data;
-
-import javax.validation.constraints.NotBlank;
-import javax.validation.constraints.NotNull;
-import javax.validation.constraints.Size;
-
-/**
- * 角色创建 Request BO
- *
- * @author 芋道源码
- */
-@Data
-public class RoleCreateReqBO {
-
-    /**
-     * 租户编号
-     */
-    @NotNull(message = "租户编号不能为空")
-    private Long tenantId;
-
-    /**
-     * 角色名称
-     */
-    @NotBlank(message = "角色名称不能为空")
-    @Size(max = 30, message = "角色名称长度不能超过30个字符")
-    private String name;
-
-    /**
-     * 角色标志
-     */
-    @NotBlank(message = "角色标志不能为空")
-    @Size(max = 100, message = "角色标志长度不能超过100个字符")
-    private String code;
-
-    /**
-     * 显示顺序
-     */
-    @NotNull(message = "显示顺序不能为空")
-    private Integer sort;
-
-    /**
-     * 角色类型
-     */
-    @NotNull(message = "角色类型不能为空")
-    private Integer type;
-
-}

+ 7 - 5
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java

@@ -142,7 +142,8 @@ public class SocialClientServiceImpl implements SocialClientService {
      * @param userType 用户类型
      * @return AuthRequest 对象
      */
-    private AuthRequest buildAuthRequest(Integer socialType, Integer userType) {
+    @VisibleForTesting
+    AuthRequest buildAuthRequest(Integer socialType, Integer userType) {
         // 1. 先查找默认的配置项,从 application-*.yaml 中读取
         AuthRequest request = authRequestFactory.get(SocialTypeEnum.valueOfType(socialType).getSource());
         Assert.notNull(request, String.format("社交平台(%d) 不存在", socialType));
@@ -180,7 +181,8 @@ public class SocialClientServiceImpl implements SocialClientService {
      * @param userType 用户类型
      * @return WxMpService 对象
      */
-    private WxMpService getWxMpService(Integer userType) {
+    @VisibleForTesting
+    WxMpService getWxMpService(Integer userType) {
         // 第一步,查询 DB 的配置项,获得对应的 WxMpService 对象
         SocialClientDO client = socialClientMapper.selectBySocialTypeAndUserType(
                 SocialTypeEnum.WECHAT_MP.getType(), userType);
@@ -198,7 +200,7 @@ public class SocialClientServiceImpl implements SocialClientService {
      * @param clientSecret 微信公众号 secret
      * @return WxMpService 对象
      */
-    private WxMpService buildWxMpService(String clientId, String clientSecret) {
+    public WxMpService buildWxMpService(String clientId, String clientSecret) {
         // 第一步,创建 WxMpRedisConfigImpl 对象
         WxMpRedisConfigImpl configStorage = new WxMpRedisConfigImpl(
                 new RedisTemplateWxRedisOps(stringRedisTemplate),
@@ -231,7 +233,8 @@ public class SocialClientServiceImpl implements SocialClientService {
      * @param userType 用户类型
      * @return WxMpService 对象
      */
-    private WxMaService getWxMaService(Integer userType) {
+    @VisibleForTesting
+    WxMaService getWxMaService(Integer userType) {
         // 第一步,查询 DB 的配置项,获得对应的 WxMaService 对象
         SocialClientDO client = socialClientMapper.selectBySocialTypeAndUserType(
                 SocialTypeEnum.WECHAT_MINI_APP.getType(), userType);
@@ -311,7 +314,6 @@ public class SocialClientServiceImpl implements SocialClientService {
      * @param userType 用户类型
      * @param socialType 社交类型
      */
-    @VisibleForTesting
     private void validateSocialClientUnique(Long id, Integer userType, Integer socialType) {
         SocialClientDO client = socialClientMapper.selectBySocialTypeAndUserType(
                 socialType, userType);

+ 3 - 6
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java

@@ -1,14 +1,12 @@
 package cn.iocoder.yudao.module.system.service.social;
 
 import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.collection.ListUtil;
 import cn.hutool.core.lang.Assert;
 import cn.iocoder.yudao.framework.common.exception.ServiceException;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
 import cn.iocoder.yudao.module.system.controller.admin.socail.vo.user.SocialUserPageReqVO;
-import cn.iocoder.yudao.module.system.convert.social.SocialUserConvert;
 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
 import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserBindMapper;
@@ -22,14 +20,14 @@ import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
 import javax.validation.constraints.NotNull;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
-import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.AUTH_THIRD_LOGIN_NOT_BIND;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SOCIAL_USER_NOT_FOUND;
 
 /**
  * 社交用户 Service 实现类
@@ -61,7 +59,7 @@ public class SocialUserServiceImpl implements SocialUserService {
     }
 
     @Override
-    @Transactional
+    @Transactional(rollbackFor = Exception.class)
     public String bindSocialUser(SocialUserBindReqDTO reqDTO) {
         // 获得社交用户
         SocialUserDO socialUser = authSocialUser(reqDTO.getSocialType(), reqDTO.getUserType(),
@@ -110,7 +108,6 @@ public class SocialUserServiceImpl implements SocialUserService {
         return new SocialUserRespDTO(socialUser.getOpenid(), socialUserBind.getUserId());
     }
 
-    // TODO 芋艿:调整下单测
     /**
      * 授权获得对应的社交用户
      * 如果授权失败,则会抛出 {@link ServiceException} 异常

+ 18 - 0
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/dept/PostServiceImplTest.java

@@ -155,6 +155,24 @@ public class PostServiceImplTest extends BaseDbUnitTest {
 
     @Test
     public void testGetPostList() {
+        // mock 数据
+        PostDO postDO01 = randomPojo(PostDO.class);
+        postMapper.insert(postDO01);
+        // 测试 id 不匹配
+        PostDO postDO02 = randomPojo(PostDO.class);
+        postMapper.insert(postDO02);
+        // 准备参数
+        List<Long> ids = singletonList(postDO01.getId());
+
+        // 调用
+        List<PostDO> list = postService.getPostList(ids);
+        // 断言
+        assertEquals(1, list.size());
+        assertPojoEquals(postDO01, list.get(0));
+    }
+
+    @Test
+    public void testGetPostList_idsAndStatus() {
         // mock 数据
         PostDO postDO01 = randomPojo(PostDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
         postMapper.insert(postDO01);

+ 8 - 3
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/dict/DictDataServiceImplTest.java

@@ -50,10 +50,15 @@ public class DictDataServiceImplTest extends BaseDbUnitTest {
         DictDataDO dictDataDO03 = randomDictDataDO().setDictType("yunai").setSort(3)
                 .setStatus(CommonStatusEnum.DISABLE.getStatus());
         dictDataMapper.insert(dictDataDO03);
+        DictDataDO dictDataDO04 = randomDictDataDO().setDictType("yunai2").setSort(3)
+                .setStatus(CommonStatusEnum.DISABLE.getStatus());
+        dictDataMapper.insert(dictDataDO04);
         // 准备参数
+        Integer status = CommonStatusEnum.ENABLE.getStatus();
+        String dictType = "yunai";
 
         // 调用
-        List<DictDataDO> dictDataDOList = dictDataService.getDictDataList();
+        List<DictDataDO> dictDataDOList = dictDataService.getDictDataList(status, dictType);
         // 断言
         assertEquals(2, dictDataDOList.size());
         assertPojoEquals(dictDataDO02, dictDataDOList.get(0));
@@ -236,7 +241,7 @@ public class DictDataServiceImplTest extends BaseDbUnitTest {
     }
 
     @Test
-    public void testCountByDictType() {
+    public void testGetDictDataCountByDictType() {
         // mock 数据
         dictDataMapper.insert(randomDictDataDO(o -> o.setDictType("yunai")));
         dictDataMapper.insert(randomDictDataDO(o -> o.setDictType("tudou")));
@@ -245,7 +250,7 @@ public class DictDataServiceImplTest extends BaseDbUnitTest {
         String dictType = "yunai";
 
         // 调用
-        long count = dictDataService.countByDictType(dictType);
+        long count = dictDataService.getDictDataCountByDictType(dictType);
         // 校验
         assertEquals(2L, count);
     }

+ 1 - 1
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/dict/DictTypeServiceImplTest.java

@@ -158,7 +158,7 @@ public class DictTypeServiceImplTest extends BaseDbUnitTest {
         // 准备参数
         Long id = dbDictType.getId();
         // mock 方法
-        when(dictDataService.countByDictType(eq(dbDictType.getType()))).thenReturn(1L);
+        when(dictDataService.getDictDataCountByDictType(eq(dbDictType.getType()))).thenReturn(1L);
 
         // 调用, 并断言异常
         assertServiceException(() -> dictTypeService.deleteDictType(id), DICT_TYPE_HAS_CHILDREN);

+ 14 - 0
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/notify/NotifySendServiceImplTest.java

@@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
 import cn.iocoder.yudao.module.system.dal.dataobject.notify.NotifyTemplateDO;
 import org.assertj.core.util.Lists;
+import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
@@ -172,5 +173,18 @@ class NotifySendServiceImplTest extends BaseMockitoUnitTest {
                 NOTIFY_SEND_TEMPLATE_PARAM_MISS, "code");
     }
 
+    @Test
+    public void testSendBatchNotify() {
+        // 准备参数
+        // mock 方法
+
+        // 调用
+        UnsupportedOperationException exception = Assertions.assertThrows(
+                UnsupportedOperationException.class,
+                () -> notifySendService.sendBatchNotify(null, null, null, null, null)
+        );
+        // 断言
+        assertEquals("暂时不支持该操作,感兴趣可以实现该功能哟!", exception.getMessage());
+    }
 
 }

+ 36 - 0
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImplTest.java

@@ -13,6 +13,8 @@ import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
 
 import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 
@@ -185,6 +187,40 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
         assertPojoEquals(menu100, result.get(0));
     }
 
+    @Test
+    public void testGetMenuIdListByPermissionFromCache() {
+        // mock 数据
+        MenuDO menu100 = randomPojo(MenuDO.class);
+        menuMapper.insert(menu100);
+        MenuDO menu101 = randomPojo(MenuDO.class);
+        menuMapper.insert(menu101);
+        // 准备参数
+        String permission = menu100.getPermission();
+
+        // 调用
+        List<Long> ids = menuService.getMenuIdListByPermissionFromCache(permission);
+        // 断言
+        assertEquals(1, ids.size());
+        assertEquals(menu100.getId(), ids.get(0));
+    }
+
+    @Test
+    public void testGetMenuList_ids() {
+        // mock 数据
+        MenuDO menu100 = randomPojo(MenuDO.class);
+        menuMapper.insert(menu100);
+        MenuDO menu101 = randomPojo(MenuDO.class);
+        menuMapper.insert(menu101);
+        // 准备参数
+        Collection<Long> ids = Collections.singleton(menu100.getId());
+
+        // 调用
+        List<MenuDO> list = menuService.getMenuList(ids);
+        // 断言
+        assertEquals(1, list.size());
+        assertPojoEquals(menu100, list.get(0));
+    }
+
     @Test
     public void testGetMenu() {
         // mock 数据

+ 33 - 0
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImplTest.java

@@ -233,6 +233,39 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
         assertPojoEquals(dbRole01, list.get(0));
     }
 
+    @Test
+    public void testGetRoleList() {
+        // mock 数据
+        RoleDO dbRole01 = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
+        roleMapper.insert(dbRole01);
+        RoleDO dbRole02 = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
+        roleMapper.insert(dbRole02);
+
+        // 调用
+        List<RoleDO> list = roleService.getRoleList();
+        // 断言
+        assertEquals(2, list.size());
+        assertPojoEquals(dbRole01, list.get(0));
+        assertPojoEquals(dbRole02, list.get(1));
+    }
+
+    @Test
+    public void testGetRoleList_ids() {
+        // mock 数据
+        RoleDO dbRole01 = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
+        roleMapper.insert(dbRole01);
+        RoleDO dbRole02 = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
+        roleMapper.insert(dbRole02);
+        // 准备参数
+        Collection<Long> ids = singleton(dbRole01.getId());
+
+        // 调用
+        List<RoleDO> list = roleService.getRoleList(ids);
+        // 断言
+        assertEquals(1, list.size());
+        assertPojoEquals(dbRole01, list.get(0));
+    }
+
     @Test
     public void testGetRoleListFromCache() {
         try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {

+ 32 - 0
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordServiceImplTest.java

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.service.sensitiveword;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
+import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
 import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordPageReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordSaveVO;
@@ -13,6 +14,8 @@ import org.junit.jupiter.api.Test;
 import org.springframework.context.annotation.Import;
 
 import javax.annotation.Resource;
+import java.time.Duration;
+import java.time.LocalDateTime;
 import java.util.Arrays;
 import java.util.List;
 
@@ -78,6 +81,35 @@ public class SensitiveWordServiceImplTest extends BaseDbUnitTest {
         assertNotNull(sensitiveWordService.getTagSensitiveWordTries().get("测试"));
     }
 
+    @Test
+    public void testRefreshLocalCache() {
+        // mock 数据
+        SensitiveWordDO wordDO1 = randomPojo(SensitiveWordDO.class, o -> o.setName("傻瓜")
+                .setTags(singletonList("论坛")).setStatus(CommonStatusEnum.ENABLE.getStatus()));
+        wordDO1.setUpdateTime(LocalDateTime.now());
+        sensitiveWordMapper.insert(wordDO1);
+        sensitiveWordService.initLocalCache();
+        // mock 数据 ②
+        SensitiveWordDO wordDO2 = randomPojo(SensitiveWordDO.class, o -> o.setName("笨蛋")
+                .setTags(singletonList("蔬菜")).setStatus(CommonStatusEnum.ENABLE.getStatus()));
+        wordDO2.setUpdateTime(LocalDateTimeUtils.addTime(Duration.ofMinutes(1))); // 避免时间相同
+        sensitiveWordMapper.insert(wordDO2);
+
+        // 调用
+        sensitiveWordService.refreshLocalCache();
+        // 断言 sensitiveWordTagsCache 缓存
+        assertEquals(SetUtils.asSet("论坛", "蔬菜"), sensitiveWordService.getSensitiveWordTagSet());
+        // 断言 sensitiveWordCache
+        assertEquals(2, sensitiveWordService.getSensitiveWordCache().size());
+        assertPojoEquals(wordDO1, sensitiveWordService.getSensitiveWordCache().get(0));
+        assertPojoEquals(wordDO2, sensitiveWordService.getSensitiveWordCache().get(1));
+        // 断言 tagSensitiveWordTries 缓存
+        assertNotNull(sensitiveWordService.getDefaultSensitiveWordTrie());
+        assertEquals(2, sensitiveWordService.getTagSensitiveWordTries().size());
+        assertNotNull(sensitiveWordService.getTagSensitiveWordTries().get("论坛"));
+        assertNotNull(sensitiveWordService.getTagSensitiveWordTries().get("蔬菜"));
+    }
+
     @Test
     public void testCreateSensitiveWord_success() {
         // 准备参数

+ 25 - 10
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsSendServiceImplTest.java

@@ -16,6 +16,7 @@ import cn.iocoder.yudao.module.system.mq.producer.sms.SmsProducer;
 import cn.iocoder.yudao.module.system.service.member.MemberService;
 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
 import org.assertj.core.util.Lists;
+import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
@@ -35,7 +36,7 @@ import static org.mockito.Mockito.*;
 public class SmsSendServiceImplTest extends BaseMockitoUnitTest {
 
     @InjectMocks
-    private SmsSendServiceImpl smsService;
+    private SmsSendServiceImpl smsSendService;
 
     @Mock
     private AdminUserService adminUserService;
@@ -80,7 +81,7 @@ public class SmsSendServiceImplTest extends BaseMockitoUnitTest {
                 eq(content), eq(templateParams))).thenReturn(smsLogId);
 
         // 调用
-        Long resultSmsLogId = smsService.sendSingleSmsToAdmin(null, userId, templateCode, templateParams);
+        Long resultSmsLogId = smsSendService.sendSingleSmsToAdmin(null, userId, templateCode, templateParams);
         // 断言
         assertEquals(smsLogId, resultSmsLogId);
         // 断言调用
@@ -119,7 +120,7 @@ public class SmsSendServiceImplTest extends BaseMockitoUnitTest {
                 eq(content), eq(templateParams))).thenReturn(smsLogId);
 
         // 调用
-        Long resultSmsLogId = smsService.sendSingleSmsToMember(null, userId, templateCode, templateParams);
+        Long resultSmsLogId = smsSendService.sendSingleSmsToMember(null, userId, templateCode, templateParams);
         // 断言
         assertEquals(smsLogId, resultSmsLogId);
         // 断言调用
@@ -159,7 +160,7 @@ public class SmsSendServiceImplTest extends BaseMockitoUnitTest {
                 eq(content), eq(templateParams))).thenReturn(smsLogId);
 
         // 调用
-        Long resultSmsLogId = smsService.sendSingleSms(mobile, userId, userType, templateCode, templateParams);
+        Long resultSmsLogId = smsSendService.sendSingleSms(mobile, userId, userType, templateCode, templateParams);
         // 断言
         assertEquals(smsLogId, resultSmsLogId);
         // 断言调用
@@ -199,7 +200,7 @@ public class SmsSendServiceImplTest extends BaseMockitoUnitTest {
                 eq(content), eq(templateParams))).thenReturn(smsLogId);
 
         // 调用
-        Long resultSmsLogId = smsService.sendSingleSms(mobile, userId, userType, templateCode, templateParams);
+        Long resultSmsLogId = smsSendService.sendSingleSms(mobile, userId, userType, templateCode, templateParams);
         // 断言
         assertEquals(smsLogId, resultSmsLogId);
         // 断言调用
@@ -214,7 +215,7 @@ public class SmsSendServiceImplTest extends BaseMockitoUnitTest {
         // mock 方法
 
         // 调用,并断言异常
-        assertServiceException(() -> smsService.validateSmsTemplate(templateCode),
+        assertServiceException(() -> smsSendService.validateSmsTemplate(templateCode),
                 SMS_SEND_TEMPLATE_NOT_EXISTS);
     }
 
@@ -227,7 +228,7 @@ public class SmsSendServiceImplTest extends BaseMockitoUnitTest {
         // mock 方法
 
         // 调用,并断言异常
-        assertServiceException(() -> smsService.buildTemplateParams(template, templateParams),
+        assertServiceException(() -> smsSendService.buildTemplateParams(template, templateParams),
                 SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS, "code");
     }
 
@@ -237,10 +238,24 @@ public class SmsSendServiceImplTest extends BaseMockitoUnitTest {
         // mock 方法
 
         // 调用,并断言异常
-        assertServiceException(() -> smsService.validateMobile(null),
+        assertServiceException(() -> smsSendService.validateMobile(null),
                 SMS_SEND_MOBILE_NOT_EXISTS);
     }
 
+    @Test
+    public void testSendBatchNotify() {
+        // 准备参数
+        // mock 方法
+
+        // 调用
+        UnsupportedOperationException exception = Assertions.assertThrows(
+                UnsupportedOperationException.class,
+                () -> smsSendService.sendBatchSms(null, null, null, null, null)
+        );
+        // 断言
+        assertEquals("暂时不支持该操作,感兴趣可以实现该功能哟!", exception.getMessage());
+    }
+
     @Test
     @SuppressWarnings("unchecked")
     public void testDoSendSms() throws Throwable {
@@ -255,7 +270,7 @@ public class SmsSendServiceImplTest extends BaseMockitoUnitTest {
                 eq(message.getTemplateParams()))).thenReturn(sendResult);
 
         // 调用
-        smsService.doSendSms(message);
+        smsSendService.doSendSms(message);
         // 断言
         verify(smsLogService).updateSmsSendResult(eq(message.getLogId()),
                 eq(sendResult.getSuccess()), eq(sendResult.getApiCode()),
@@ -274,7 +289,7 @@ public class SmsSendServiceImplTest extends BaseMockitoUnitTest {
         List<SmsReceiveRespDTO> receiveResults = randomPojoList(SmsReceiveRespDTO.class);
 
         // 调用
-        smsService.receiveSmsStatus(channelCode, text);
+        smsSendService.receiveSmsStatus(channelCode, text);
         // 断言
         receiveResults.forEach(result -> smsLogService.updateSmsReceiveResult(eq(result.getLogId()), eq(result.getSuccess()),
                 eq(result.getReceiveTime()), eq(result.getErrorCode()), eq(result.getErrorCode())));

+ 60 - 1
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceImplTest.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.system.service.sms;
 
+import cn.hutool.core.map.MapUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
@@ -8,8 +9,8 @@ import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
 import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
 import cn.iocoder.yudao.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
-import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateSaveReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplatePageReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateSaveReqVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
 import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsTemplateMapper;
@@ -21,6 +22,7 @@ import org.springframework.context.annotation.Import;
 
 import javax.annotation.Resource;
 import java.util.List;
+import java.util.Map;
 import java.util.function.Consumer;
 
 import static cn.hutool.core.util.RandomUtil.randomEle;
@@ -48,6 +50,19 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest {
     @MockBean
     private SmsClient smsClient;
 
+    @Test
+    public void testFormatSmsTemplateContent() {
+        // 准备参数
+        String content = "正在进行登录操作{operation},您的验证码是{code}";
+        Map<String, Object> params = MapUtil.<String, Object>builder("operation", "登录")
+                .put("code", "1234").build();
+
+        // 调用
+        String result = smsTemplateService.formatSmsTemplateContent(content, params);
+        // 断言
+        assertEquals("正在进行登录操作登录,您的验证码是1234", result);
+    }
+
     @Test
     public void testParseTemplateContentParams() {
         // 准备参数
@@ -156,6 +171,34 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest {
         assertServiceException(() -> smsTemplateService.deleteSmsTemplate(id), SMS_TEMPLATE_NOT_EXISTS);
     }
 
+    @Test
+    public void testGetSmsTemplate() {
+        // mock 数据
+        SmsTemplateDO dbSmsTemplate = randomSmsTemplateDO();
+        smsTemplateMapper.insert(dbSmsTemplate);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbSmsTemplate.getId();
+
+        // 调用
+        SmsTemplateDO smsTemplate = smsTemplateService.getSmsTemplate(id);
+        // 校验
+        assertPojoEquals(dbSmsTemplate, smsTemplate);
+    }
+
+    @Test
+    public void testGetSmsTemplateByCodeFromCache() {
+        // mock 数据
+        SmsTemplateDO dbSmsTemplate = randomSmsTemplateDO();
+        smsTemplateMapper.insert(dbSmsTemplate);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        String code = dbSmsTemplate.getCode();
+
+        // 调用
+        SmsTemplateDO smsTemplate = smsTemplateService.getSmsTemplateByCodeFromCache(code);
+        // 校验
+        assertPojoEquals(dbSmsTemplate, smsTemplate);
+    }
+
     @Test
     public void testGetSmsTemplatePage() {
         // mock 数据
@@ -201,6 +244,22 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest {
         assertPojoEquals(dbSmsTemplate, pageResult.getList().get(0));
     }
 
+    @Test
+    public void testGetSmsTemplateCountByChannelId() {
+        // mock 数据
+        SmsTemplateDO dbSmsTemplate = randomPojo(SmsTemplateDO.class, o -> o.setChannelId(1L));
+        smsTemplateMapper.insert(dbSmsTemplate);
+        // 测试 channelId 不匹配
+        smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setChannelId(2L)));
+        // 准备参数
+        Long channelId = 1L;
+
+        // 调用
+        Long count = smsTemplateService.getSmsTemplateCountByChannelId(channelId);
+        // 断言
+        assertEquals(1, count);
+    }
+
     @Test
     public void testValidateSmsChannel_success() {
         // 准备参数

+ 354 - 29
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImplTest.java

@@ -1,33 +1,53 @@
 package cn.iocoder.yudao.module.system.service.social;
 
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.api.WxMaUserService;
+import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
+import cn.hutool.core.util.ReflectUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
 import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientSaveReqVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialClientDO;
 import cn.iocoder.yudao.module.system.dal.mysql.social.SocialClientMapper;
-import org.junit.jupiter.api.Disabled;
+import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
+import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaProperties;
+import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties;
+import com.xingyuv.jushauth.config.AuthConfig;
+import com.xingyuv.jushauth.model.AuthResponse;
+import com.xingyuv.jushauth.model.AuthUser;
+import com.xingyuv.jushauth.request.AuthDefaultRequest;
+import com.xingyuv.jushauth.request.AuthRequest;
+import com.xingyuv.jushauth.utils.AuthStateUtils;
+import com.xingyuv.justauth.AuthRequestFactory;
+import me.chanjar.weixin.common.bean.WxJsapiSignature;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.mp.api.WxMpService;
 import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
+import org.springframework.data.redis.core.StringRedisTemplate;
 
 import javax.annotation.Resource;
 
+import static cn.hutool.core.util.RandomUtil.randomEle;
 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
-import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
-import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
-import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SOCIAL_CLIENT_NOT_EXISTS;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
 import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
 
-// TODO 芋艿:单测后续补充下;
 /**
  * {@link SocialClientServiceImpl} 的单元测试类
  *
  * @author 芋道源码
  */
 @Import(SocialClientServiceImpl.class)
-@Disabled
 public class SocialClientServiceImplTest extends BaseDbUnitTest {
 
     @Resource
@@ -36,10 +56,305 @@ public class SocialClientServiceImplTest extends BaseDbUnitTest {
     @Resource
     private SocialClientMapper socialClientMapper;
 
+    @MockBean
+    private AuthRequestFactory authRequestFactory;
+
+    @MockBean
+    private WxMpService wxMpService;
+    @MockBean
+    private WxMpProperties wxMpProperties;
+    @MockBean
+    private StringRedisTemplate stringRedisTemplate;
+    @MockBean
+    private WxMaService wxMaService;
+    @MockBean
+    private WxMaProperties wxMaProperties;
+
+    @Test
+    public void testGetAuthorizeUrl() {
+        try (MockedStatic<AuthStateUtils> authStateUtilsMock = mockStatic(AuthStateUtils.class)) {
+            // 准备参数
+            Integer socialType = SocialTypeEnum.WECHAT_MP.getType();
+            Integer userType = randomPojo(UserTypeEnum.class).getValue();
+            String redirectUri = "sss";
+            // mock 获得对应的 AuthRequest 实现
+            AuthRequest authRequest = mock(AuthRequest.class);
+            when(authRequestFactory.get(eq("WECHAT_MP"))).thenReturn(authRequest);
+            // mock 方法
+            authStateUtilsMock.when(AuthStateUtils::createState).thenReturn("aoteman");
+            when(authRequest.authorize(eq("aoteman"))).thenReturn("https://www.iocoder.cn?redirect_uri=yyy");
+
+            // 调用
+            String url = socialClientService.getAuthorizeUrl(socialType, userType, redirectUri);
+            // 断言
+            assertEquals("https://www.iocoder.cn?redirect_uri=sss", url);
+        }
+    }
+
+    @Test
+    public void testAuthSocialUser_success() {
+        // 准备参数
+        Integer socialType = SocialTypeEnum.WECHAT_MP.getType();
+        Integer userType = randomPojo(UserTypeEnum.class).getValue();
+        String code = randomString();
+        String state = randomString();
+        // mock 方法(AuthRequest)
+        AuthRequest authRequest = mock(AuthRequest.class);
+        when(authRequestFactory.get(eq("WECHAT_MP"))).thenReturn(authRequest);
+        // mock 方法(AuthResponse)
+        AuthUser authUser = randomPojo(AuthUser.class);
+        AuthResponse<?> authResponse = new AuthResponse<>(2000, null, authUser);
+        when(authRequest.login(argThat(authCallback -> {
+            assertEquals(code, authCallback.getCode());
+            assertEquals(state, authCallback.getState());
+            return true;
+        }))).thenReturn(authResponse);
+
+        // 调用
+        AuthUser result = socialClientService.getAuthUser(socialType, userType, code, state);
+        // 断言
+        assertSame(authUser, result);
+    }
+
+    @Test
+    public void testAuthSocialUser_fail() {
+        // 准备参数
+        Integer socialType = SocialTypeEnum.WECHAT_MP.getType();
+        Integer userType = randomPojo(UserTypeEnum.class).getValue();
+        String code = randomString();
+        String state = randomString();
+        // mock 方法(AuthRequest)
+        AuthRequest authRequest = mock(AuthRequest.class);
+        when(authRequestFactory.get(eq("WECHAT_MP"))).thenReturn(authRequest);
+        // mock 方法(AuthResponse)
+        AuthResponse<?> authResponse = new AuthResponse<>(0, "模拟失败", null);
+        when(authRequest.login(argThat(authCallback -> {
+            assertEquals(code, authCallback.getCode());
+            assertEquals(state, authCallback.getState());
+            return true;
+        }))).thenReturn(authResponse);
+
+        // 调用并断言
+        assertServiceException(
+                () -> socialClientService.getAuthUser(socialType, userType, code, state),
+                SOCIAL_USER_AUTH_FAILURE, "模拟失败");
+    }
+
+    @Test
+    public void testBuildAuthRequest_clientNull() {
+        // 准备参数
+        Integer socialType = SocialTypeEnum.WECHAT_MP.getType();
+        Integer userType = randomPojo(SocialTypeEnum.class).getType();
+        // mock 获得对应的 AuthRequest 实现
+        AuthRequest authRequest = mock(AuthDefaultRequest.class);
+        AuthConfig authConfig = (AuthConfig) ReflectUtil.getFieldValue(authRequest, "config");
+        when(authRequestFactory.get(eq("WECHAT_MP"))).thenReturn(authRequest);
+
+        // 调用
+        AuthRequest result = socialClientService.buildAuthRequest(socialType, userType);
+        // 断言
+        assertSame(authRequest, result);
+        assertSame(authConfig, ReflectUtil.getFieldValue(authConfig, "config"));
+    }
+
+    @Test
+    public void testBuildAuthRequest_clientDisable() {
+        // 准备参数
+        Integer socialType = SocialTypeEnum.WECHAT_MP.getType();
+        Integer userType = randomPojo(SocialTypeEnum.class).getType();
+        // mock 获得对应的 AuthRequest 实现
+        AuthRequest authRequest = mock(AuthDefaultRequest.class);
+        AuthConfig authConfig = (AuthConfig) ReflectUtil.getFieldValue(authRequest, "config");
+        when(authRequestFactory.get(eq("WECHAT_MP"))).thenReturn(authRequest);
+        // mock 数据
+        SocialClientDO client = randomPojo(SocialClientDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())
+                .setUserType(userType).setSocialType(socialType));
+        socialClientMapper.insert(client);
+
+        // 调用
+        AuthRequest result = socialClientService.buildAuthRequest(socialType, userType);
+        // 断言
+        assertSame(authRequest, result);
+        assertSame(authConfig, ReflectUtil.getFieldValue(authConfig, "config"));
+    }
+
+    @Test
+    public void testBuildAuthRequest_clientEnable() {
+        // 准备参数
+        Integer socialType = SocialTypeEnum.WECHAT_MP.getType();
+        Integer userType = randomPojo(SocialTypeEnum.class).getType();
+        // mock 获得对应的 AuthRequest 实现
+        AuthConfig authConfig = mock(AuthConfig.class);
+        AuthRequest authRequest = mock(AuthDefaultRequest.class);
+        ReflectUtil.setFieldValue(authRequest, "config", authConfig);
+        when(authRequestFactory.get(eq("WECHAT_MP"))).thenReturn(authRequest);
+        // mock 数据
+        SocialClientDO client = randomPojo(SocialClientDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())
+                .setUserType(userType).setSocialType(socialType));
+        socialClientMapper.insert(client);
+
+        // 调用
+        AuthRequest result = socialClientService.buildAuthRequest(socialType, userType);
+        // 断言
+        assertSame(authRequest, result);
+        assertNotSame(authConfig, ReflectUtil.getFieldValue(authRequest, "config"));
+    }
+
+    // =================== 微信公众号独有 ===================
+
+    @Test
+    public void testCreateWxMpJsapiSignature() throws WxErrorException {
+        // 准备参数
+        Integer userType = randomPojo(UserTypeEnum.class).getValue();
+        String url = randomString();
+        // mock 方法
+        WxJsapiSignature signature = randomPojo(WxJsapiSignature.class);
+        when(wxMpService.createJsapiSignature(eq(url))).thenReturn(signature);
+
+        // 调用
+        WxJsapiSignature result = socialClientService.createWxMpJsapiSignature(userType, url);
+        // 断言
+        assertSame(signature, result);
+    }
+
+    @Test
+    public void testGetWxMpService_clientNull() {
+        // 准备参数
+        Integer userType = randomPojo(UserTypeEnum.class).getValue();
+        // mock 方法
+
+        // 调用
+        WxMpService result = socialClientService.getWxMpService(userType);
+        // 断言
+        assertSame(wxMpService, result);
+    }
+
+    @Test
+    public void testGetWxMpService_clientDisable() {
+        // 准备参数
+        Integer userType = randomPojo(UserTypeEnum.class).getValue();
+        // mock 数据
+        SocialClientDO client = randomPojo(SocialClientDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())
+                .setUserType(userType).setSocialType(SocialTypeEnum.WECHAT_MP.getType()));
+        socialClientMapper.insert(client);
+
+        // 调用
+        WxMpService result = socialClientService.getWxMpService(userType);
+        // 断言
+        assertSame(wxMpService, result);
+    }
+
+    @Test
+    public void testGetWxMpService_clientEnable() {
+        // 准备参数
+        Integer userType = randomPojo(UserTypeEnum.class).getValue();
+        // mock 数据
+        SocialClientDO client = randomPojo(SocialClientDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())
+                .setUserType(userType).setSocialType(SocialTypeEnum.WECHAT_MP.getType()));
+        socialClientMapper.insert(client);
+        // mock 方法
+        WxMpProperties.ConfigStorage configStorage = mock(WxMpProperties.ConfigStorage.class);
+        when(wxMpProperties.getConfigStorage()).thenReturn(configStorage);
+
+        // 调用
+        WxMpService result = socialClientService.getWxMpService(userType);
+        // 断言
+        assertNotSame(wxMpService, result);
+        assertEquals(client.getClientId(), result.getWxMpConfigStorage().getAppId());
+        assertEquals(client.getClientSecret(), result.getWxMpConfigStorage().getSecret());
+    }
+
+    // =================== 微信小程序独有 ===================
+
+    @Test
+    public void testGetWxMaPhoneNumberInfo_success() throws WxErrorException {
+        // 准备参数
+        Integer userType = randomPojo(UserTypeEnum.class).getValue();
+        String phoneCode = randomString();
+        // mock 方法
+        WxMaUserService userService = mock(WxMaUserService.class);
+        when(wxMaService.getUserService()).thenReturn(userService);
+        WxMaPhoneNumberInfo phoneNumber = randomPojo(WxMaPhoneNumberInfo.class);
+        when(userService.getPhoneNoInfo(eq(phoneCode))).thenReturn(phoneNumber);
+
+        // 调用
+        WxMaPhoneNumberInfo result = socialClientService.getWxMaPhoneNumberInfo(userType, phoneCode);
+        // 断言
+        assertSame(phoneNumber, result);
+    }
+
+    @Test
+    public void testGetWxMaPhoneNumberInfo_exception() throws WxErrorException {
+        // 准备参数
+        Integer userType = randomPojo(UserTypeEnum.class).getValue();
+        String phoneCode = randomString();
+        // mock 方法
+        WxMaUserService userService = mock(WxMaUserService.class);
+        when(wxMaService.getUserService()).thenReturn(userService);
+        WxErrorException wxErrorException = randomPojo(WxErrorException.class);
+        when(userService.getPhoneNoInfo(eq(phoneCode))).thenThrow(wxErrorException);
+
+        // 调用并断言异常
+        assertServiceException(() -> socialClientService.getWxMaPhoneNumberInfo(userType, phoneCode),
+                SOCIAL_CLIENT_WEIXIN_MINI_APP_PHONE_CODE_ERROR);
+    }
+
+    @Test
+    public void testGetWxMaService_clientNull() {
+        // 准备参数
+        Integer userType = randomPojo(UserTypeEnum.class).getValue();
+        // mock 方法
+
+        // 调用
+        WxMaService result = socialClientService.getWxMaService(userType);
+        // 断言
+        assertSame(wxMaService, result);
+    }
+
+    @Test
+    public void testGetWxMaService_clientDisable() {
+        // 准备参数
+        Integer userType = randomPojo(UserTypeEnum.class).getValue();
+        // mock 数据
+        SocialClientDO client = randomPojo(SocialClientDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())
+                .setUserType(userType).setSocialType(SocialTypeEnum.WECHAT_MINI_APP.getType()));
+        socialClientMapper.insert(client);
+
+        // 调用
+        WxMaService result = socialClientService.getWxMaService(userType);
+        // 断言
+        assertSame(wxMaService, result);
+    }
+
+    @Test
+    public void testGetWxMaService_clientEnable() {
+        // 准备参数
+        Integer userType = randomPojo(UserTypeEnum.class).getValue();
+        // mock 数据
+        SocialClientDO client = randomPojo(SocialClientDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())
+                .setUserType(userType).setSocialType(SocialTypeEnum.WECHAT_MINI_APP.getType()));
+        socialClientMapper.insert(client);
+        // mock 方法
+        WxMaProperties.ConfigStorage configStorage = mock(WxMaProperties.ConfigStorage.class);
+        when(wxMaProperties.getConfigStorage()).thenReturn(configStorage);
+
+        // 调用
+        WxMaService result = socialClientService.getWxMaService(userType);
+        // 断言
+        assertNotSame(wxMaService, result);
+        assertEquals(client.getClientId(), result.getWxMaConfig().getAppid());
+        assertEquals(client.getClientSecret(), result.getWxMaConfig().getSecret());
+    }
+
+    // =================== 客户端管理 ===================
+
     @Test
     public void testCreateSocialClient_success() {
         // 准备参数
-        SocialClientSaveReqVO reqVO = randomPojo(SocialClientSaveReqVO.class)
+        SocialClientSaveReqVO reqVO = randomPojo(SocialClientSaveReqVO.class,
+                o -> o.setSocialType(randomEle(SocialTypeEnum.values()).getType())
+                        .setUserType(randomEle(UserTypeEnum.values()).getValue())
+                        .setStatus(randomCommonStatus()))
                 .setId(null); // 防止 id 被赋值
 
         // 调用
@@ -59,6 +374,9 @@ public class SocialClientServiceImplTest extends BaseDbUnitTest {
         // 准备参数
         SocialClientSaveReqVO reqVO = randomPojo(SocialClientSaveReqVO.class, o -> {
             o.setId(dbSocialClient.getId()); // 设置更新的 ID
+            o.setSocialType(randomEle(SocialTypeEnum.values()).getType())
+                    .setUserType(randomEle(UserTypeEnum.values()).getValue())
+                    .setStatus(randomCommonStatus());
         });
 
         // 调用
@@ -101,40 +419,47 @@ public class SocialClientServiceImplTest extends BaseDbUnitTest {
     }
 
     @Test
-    @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
+    public void testGetSocialClient() {
+        // mock 数据
+        SocialClientDO dbSocialClient = randomPojo(SocialClientDO.class);
+        socialClientMapper.insert(dbSocialClient);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbSocialClient.getId();
+
+        // 调用
+        SocialClientDO socialClient = socialClientService.getSocialClient(id);
+        // 校验数据正确
+        assertPojoEquals(dbSocialClient, socialClient);
+    }
+
+    @Test
     public void testGetSocialClientPage() {
         // mock 数据
         SocialClientDO dbSocialClient = randomPojo(SocialClientDO.class, o -> { // 等会查询到
-            o.setName(null);
-            o.setSocialType(null);
-            o.setUserType(null);
-            o.setClientId(null);
-            o.setClientSecret(null);
-            o.setStatus(null);
-            o.setCreateTime(null);
+            o.setName("芋头");
+            o.setSocialType(SocialTypeEnum.GITEE.getType());
+            o.setUserType(UserTypeEnum.ADMIN.getValue());
+            o.setClientId("yudao");
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
         });
         socialClientMapper.insert(dbSocialClient);
         // 测试 name 不匹配
-        socialClientMapper.insert(cloneIgnoreId(dbSocialClient, o -> o.setName(null)));
+        socialClientMapper.insert(cloneIgnoreId(dbSocialClient, o -> o.setName(randomString())));
         // 测试 socialType 不匹配
-        socialClientMapper.insert(cloneIgnoreId(dbSocialClient, o -> o.setSocialType(null)));
+        socialClientMapper.insert(cloneIgnoreId(dbSocialClient, o -> o.setSocialType(SocialTypeEnum.DINGTALK.getType())));
         // 测试 userType 不匹配
-        socialClientMapper.insert(cloneIgnoreId(dbSocialClient, o -> o.setUserType(null)));
+        socialClientMapper.insert(cloneIgnoreId(dbSocialClient, o -> o.setUserType(UserTypeEnum.MEMBER.getValue())));
         // 测试 clientId 不匹配
-        socialClientMapper.insert(cloneIgnoreId(dbSocialClient, o -> o.setClientId(null)));
-        // 测试 clientSecret 不匹配
-        socialClientMapper.insert(cloneIgnoreId(dbSocialClient, o -> o.setClientSecret(null)));
+        socialClientMapper.insert(cloneIgnoreId(dbSocialClient, o -> o.setClientId("dao")));
         // 测试 status 不匹配
-        socialClientMapper.insert(cloneIgnoreId(dbSocialClient, o -> o.setStatus(null)));
-        // 测试 createTime 不匹配
-        socialClientMapper.insert(cloneIgnoreId(dbSocialClient, o -> o.setCreateTime(null)));
+        socialClientMapper.insert(cloneIgnoreId(dbSocialClient, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
         // 准备参数
         SocialClientPageReqVO reqVO = new SocialClientPageReqVO();
-        reqVO.setName(null);
-        reqVO.setSocialType(null);
-        reqVO.setUserType(null);
-        reqVO.setClientId(null);
-        reqVO.setStatus(null);
+        reqVO.setName("芋");
+        reqVO.setSocialType(SocialTypeEnum.GITEE.getType());
+        reqVO.setUserType(UserTypeEnum.ADMIN.getValue());
+        reqVO.setClientId("yu");
+        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
 
         // 调用
         PageResult<SocialClientDO> pageResult = socialClientService.getSocialClientPage(reqVO);

+ 148 - 123
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImplTest.java

@@ -1,21 +1,17 @@
 package cn.iocoder.yudao.module.system.service.social;
 
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
+import cn.iocoder.yudao.module.system.controller.admin.socail.vo.user.SocialUserPageReqVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
 import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserBindMapper;
 import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserMapper;
 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
-import com.xingyuv.jushauth.enums.AuthResponseStatus;
-import com.xingyuv.jushauth.model.AuthCallback;
-import com.xingyuv.jushauth.model.AuthResponse;
 import com.xingyuv.jushauth.model.AuthUser;
-import com.xingyuv.jushauth.request.AuthRequest;
-import com.xingyuv.justauth.AuthRequestFactory;
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
@@ -23,18 +19,27 @@ import org.springframework.context.annotation.Import;
 import javax.annotation.Resource;
 import java.util.List;
 
-import static cn.hutool.core.util.RandomUtil.*;
+import static cn.hutool.core.util.RandomUtil.randomEle;
+import static cn.hutool.core.util.RandomUtil.randomLong;
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
-import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SOCIAL_USER_AUTH_FAILURE;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SOCIAL_USER_NOT_FOUND;
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.when;
 
+/**
+ * {@link SocialUserServiceImpl} 的单元测试类
+ *
+ * @author 芋道源码
+ */
 @Import(SocialUserServiceImpl.class)
-@Disabled // TODO 芋艿:后续统一修复
 public class SocialUserServiceImplTest extends BaseDbUnitTest {
 
     @Resource
@@ -46,119 +51,7 @@ public class SocialUserServiceImplTest extends BaseDbUnitTest {
     private SocialUserBindMapper socialUserBindMapper;
 
     @MockBean
-    private AuthRequestFactory authRequestFactory;
-
-    // TODO 芋艿:后续统一修复
-//    @Test
-//    public void testGetAuthorizeUrl() {
-//        try (MockedStatic<AuthStateUtils> authStateUtilsMock = mockStatic(AuthStateUtils.class)) {
-//            // 准备参数
-//            Integer type = SocialTypeEnum.WECHAT_MP.getType();
-//            String redirectUri = "sss";
-//            // mock 获得对应的 AuthRequest 实现
-//            AuthRequest authRequest = mock(AuthRequest.class);
-//            when(authRequestFactory.get(eq("WECHAT_MP"))).thenReturn(authRequest);
-//            // mock 方法
-//            authStateUtilsMock.when(AuthStateUtils::createState).thenReturn("aoteman");
-//            when(authRequest.authorize(eq("aoteman"))).thenReturn("https://www.iocoder.cn?redirect_uri=yyy");
-//
-//            // 调用
-//            String url = socialUserService.getAuthorizeUrl(type, redirectUri);
-//            // 断言
-//            assertEquals("https://www.iocoder.cn?redirect_uri=sss", url);
-//        }
-//    }
-
-    @Test
-    public void testAuthSocialUser_exists() {
-        // 准备参数
-        Integer socialType = SocialTypeEnum.GITEE.getType();
-        Integer userType = randomEle(SocialTypeEnum.values()).getType();
-        String code = "tudou";
-        String state = "yuanma";
-        // mock 方法
-        SocialUserDO socialUser = randomPojo(SocialUserDO.class).setType(socialType).setCode(code).setState(state);
-        socialUserMapper.insert(socialUser);
-
-        // 调用
-        SocialUserDO result = socialUserService.authSocialUser(socialType, userType, code, state);
-        // 断言
-        assertPojoEquals(socialUser, result);
-    }
-
-    @Test
-    public void testAuthSocialUser_authFailure() {
-        // 准备参数
-        Integer socialType = SocialTypeEnum.GITEE.getType();
-        Integer userType = randomEle(SocialTypeEnum.values()).getType();
-        // mock 方法
-        AuthRequest authRequest = mock(AuthRequest.class);
-        when(authRequestFactory.get(anyString())).thenReturn(authRequest);
-        AuthResponse<?> authResponse = new AuthResponse<>(0, "模拟失败", null);
-        when(authRequest.login(any(AuthCallback.class))).thenReturn(authResponse);
-
-        // 调用并断言
-        assertServiceException(
-                () -> socialUserService.authSocialUser(socialType, userType, randomString(10), randomString(10)),
-                SOCIAL_USER_AUTH_FAILURE, "模拟失败");
-    }
-
-    @Test
-    public void testAuthSocialUser_insert() {
-        // 准备参数
-        Integer socialType = SocialTypeEnum.GITEE.getType();
-        Integer userType = randomEle(SocialTypeEnum.values()).getType();
-        String code = "tudou";
-        String state = "yuanma";
-        // mock 方法
-        AuthRequest authRequest = mock(AuthRequest.class);
-        when(authRequestFactory.get(eq(SocialTypeEnum.GITEE.getSource()))).thenReturn(authRequest);
-        AuthUser authUser = randomPojo(AuthUser.class);
-        AuthResponse<AuthUser> authResponse = new AuthResponse<>(AuthResponseStatus.SUCCESS.getCode(), null, authUser);
-        when(authRequest.login(any(AuthCallback.class))).thenReturn(authResponse);
-
-        // 调用
-        SocialUserDO result = socialUserService.authSocialUser(socialType, userType, code, state);
-        // 断言
-        assertBindSocialUser(socialType, result, authResponse.getData());
-        assertEquals(code, result.getCode());
-        assertEquals(state, result.getState());
-    }
-
-    @Test
-    public void testAuthSocialUser_update() {
-        // 准备参数
-        Integer socialType = SocialTypeEnum.GITEE.getType();
-        Integer userType = randomEle(SocialTypeEnum.values()).getType();
-        String code = "tudou";
-        String state = "yuanma";
-        // mock 数据
-        socialUserMapper.insert(randomPojo(SocialUserDO.class).setType(socialType).setOpenid("test_openid"));
-        // mock 方法
-        AuthRequest authRequest = mock(AuthRequest.class);
-        when(authRequestFactory.get(eq(SocialTypeEnum.GITEE.getSource()))).thenReturn(authRequest);
-        AuthUser authUser = randomPojo(AuthUser.class);
-        authUser.getToken().setOpenId("test_openid");
-        AuthResponse<AuthUser> authResponse = new AuthResponse<>(AuthResponseStatus.SUCCESS.getCode(), null, authUser);
-        when(authRequest.login(any(AuthCallback.class))).thenReturn(authResponse);
-
-        // 调用
-        SocialUserDO result = socialUserService.authSocialUser(socialType, userType, code, state);
-        // 断言
-        assertBindSocialUser(socialType, result, authResponse.getData());
-        assertEquals(code, result.getCode());
-        assertEquals(state, result.getState());
-    }
-
-    private void assertBindSocialUser(Integer type, SocialUserDO socialUser, AuthUser authUser) {
-        assertEquals(authUser.getToken().getAccessToken(), socialUser.getToken());
-        assertEquals(toJsonString(authUser.getToken()), socialUser.getRawTokenInfo());
-        assertEquals(authUser.getNickname(), socialUser.getNickname());
-        assertEquals(authUser.getAvatar(), socialUser.getAvatar());
-        assertEquals(toJsonString(authUser.getRawUserInfo()), socialUser.getRawUserInfo());
-        assertEquals(type, socialUser.getType());
-        assertEquals(authUser.getUuid(), socialUser.getOpenid());
-    }
+    private SocialClientService socialClientService;
 
     @Test
     public void testGetSocialUserList() {
@@ -260,4 +153,136 @@ public class SocialUserServiceImplTest extends BaseDbUnitTest {
         assertEquals(socialUserDO.getOpenid(), socialUser.getOpenid());
     }
 
+    @Test
+    public void testAuthSocialUser_exists() {
+        // 准备参数
+        Integer socialType = SocialTypeEnum.GITEE.getType();
+        Integer userType = randomEle(SocialTypeEnum.values()).getType();
+        String code = "tudou";
+        String state = "yuanma";
+        // mock 方法
+        SocialUserDO socialUser = randomPojo(SocialUserDO.class).setType(socialType).setCode(code).setState(state);
+        socialUserMapper.insert(socialUser);
+
+        // 调用
+        SocialUserDO result = socialUserService.authSocialUser(socialType, userType, code, state);
+        // 断言
+        assertPojoEquals(socialUser, result);
+    }
+
+    @Test
+    public void testAuthSocialUser_notNull() {
+        // mock 数据
+        SocialUserDO socialUser = randomPojo(SocialUserDO.class,
+                o -> o.setType(SocialTypeEnum.GITEE.getType()).setCode("tudou").setState("yuanma"));
+        socialUserMapper.insert(socialUser);
+        // 准备参数
+        Integer socialType = SocialTypeEnum.GITEE.getType();
+        Integer userType = randomEle(SocialTypeEnum.values()).getType();
+        String code = "tudou";
+        String state = "yuanma";
+
+        // 调用
+        SocialUserDO result = socialUserService.authSocialUser(socialType, userType, code, state);
+        // 断言
+        assertPojoEquals(socialUser, result);
+    }
+
+    @Test
+    public void testAuthSocialUser_insert() {
+        // 准备参数
+        Integer socialType = SocialTypeEnum.GITEE.getType();
+        Integer userType = randomEle(SocialTypeEnum.values()).getType();
+        String code = "tudou";
+        String state = "yuanma";
+        // mock 方法
+        AuthUser authUser = randomPojo(AuthUser.class);
+        when(socialClientService.getAuthUser(eq(socialType), eq(userType), eq(code), eq(state))).thenReturn(authUser);
+
+        // 调用
+        SocialUserDO result = socialUserService.authSocialUser(socialType, userType, code, state);
+        // 断言
+        assertBindSocialUser(socialType, result, authUser);
+        assertEquals(code, result.getCode());
+        assertEquals(state, result.getState());
+    }
+
+    @Test
+    public void testAuthSocialUser_update() {
+        // 准备参数
+        Integer socialType = SocialTypeEnum.GITEE.getType();
+        Integer userType = randomEle(SocialTypeEnum.values()).getType();
+        String code = "tudou";
+        String state = "yuanma";
+        // mock 数据
+        socialUserMapper.insert(randomPojo(SocialUserDO.class).setType(socialType).setOpenid("test_openid"));
+        // mock 方法
+        AuthUser authUser = randomPojo(AuthUser.class);
+        when(socialClientService.getAuthUser(eq(socialType), eq(userType), eq(code), eq(state))).thenReturn(authUser);
+
+        // 调用
+        SocialUserDO result = socialUserService.authSocialUser(socialType, userType, code, state);
+        // 断言
+        assertBindSocialUser(socialType, result, authUser);
+        assertEquals(code, result.getCode());
+        assertEquals(state, result.getState());
+    }
+
+    private void assertBindSocialUser(Integer type, SocialUserDO socialUser, AuthUser authUser) {
+        assertEquals(authUser.getToken().getAccessToken(), socialUser.getToken());
+        assertEquals(toJsonString(authUser.getToken()), socialUser.getRawTokenInfo());
+        assertEquals(authUser.getNickname(), socialUser.getNickname());
+        assertEquals(authUser.getAvatar(), socialUser.getAvatar());
+        assertEquals(toJsonString(authUser.getRawUserInfo()), socialUser.getRawUserInfo());
+        assertEquals(type, socialUser.getType());
+        assertEquals(authUser.getUuid(), socialUser.getOpenid());
+    }
+
+    @Test
+    public void testGetSocialUser_id() {
+        // mock 数据
+        SocialUserDO socialUserDO = randomPojo(SocialUserDO.class);
+        socialUserMapper.insert(socialUserDO);
+        // 参数准备
+        Long id = socialUserDO.getId();
+
+        // 调用
+        SocialUserDO dbSocialUserDO = socialUserService.getSocialUser(id);
+        // 断言
+        assertPojoEquals(socialUserDO, dbSocialUserDO);
+    }
+
+    @Test
+    public void testGetSocialUserPage() {
+        // mock 数据
+        SocialUserDO dbSocialUser = randomPojo(SocialUserDO.class, o -> { // 等会查询到
+            o.setType(SocialTypeEnum.GITEE.getType());
+            o.setNickname("芋艿");
+            o.setOpenid("yudaoyuanma");
+            o.setCreateTime(buildTime(2020, 1, 15));
+        });
+        socialUserMapper.insert(dbSocialUser);
+        // 测试 type 不匹配
+        socialUserMapper.insert(cloneIgnoreId(dbSocialUser, o -> o.setType(SocialTypeEnum.DINGTALK.getType())));
+        // 测试 nickname 不匹配
+        socialUserMapper.insert(cloneIgnoreId(dbSocialUser, o -> o.setNickname(randomString())));
+        // 测试 openid 不匹配
+        socialUserMapper.insert(cloneIgnoreId(dbSocialUser, o -> o.setOpenid("java")));
+        // 测试 createTime 不匹配
+        socialUserMapper.insert(cloneIgnoreId(dbSocialUser, o -> o.setCreateTime(buildTime(2020, 1, 21))));
+        // 准备参数
+        SocialUserPageReqVO reqVO = new SocialUserPageReqVO();
+        reqVO.setType(SocialTypeEnum.GITEE.getType());
+        reqVO.setNickname("芋");
+        reqVO.setOpenid("yudao");
+        reqVO.setCreateTime(buildBetweenTime(2020, 1, 10, 2020, 1, 20));
+
+        // 调用
+        PageResult<SocialUserDO> pageResult = socialUserService.getSocialUserPage(reqVO);
+        // 断言
+        assertEquals(1, pageResult.getTotal());
+        assertEquals(1, pageResult.getList().size());
+        assertPojoEquals(dbSocialUser, pageResult.getList().get(0));
+    }
+
 }

+ 12 - 0
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImplTest.java

@@ -331,6 +331,18 @@ public class TenantServiceImplTest extends BaseDbUnitTest {
         assertPojoEquals(result, dbTenant);
     }
 
+    @Test
+    public void testGetTenantByWebsite() {
+        // mock 数据
+        TenantDO dbTenant = randomPojo(TenantDO.class, o -> o.setWebsite("https://www.iocoder.cn"));
+        tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据
+
+        // 调用
+        TenantDO result = tenantService.getTenantByWebsite("https://www.iocoder.cn");
+        // 校验存在
+        assertPojoEquals(result, dbTenant);
+    }
+
     @Test
     public void testGetTenantListByPackageId() {
         // mock 数据

+ 2 - 1
yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql

@@ -361,13 +361,14 @@ CREATE TABLE IF NOT EXISTS "system_social_client" (
   "user_type" int NOT NULL,
   "client_id" varchar(255) NOT NULL,
   "client_secret" varchar(255) NOT NULL,
+  "agent_id" varchar(255) NOT NULL,
   "status" int NOT NULL,
   "creator" varchar(64) DEFAULT '',
   "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
   "updater" varchar(64) DEFAULT '',
   "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
   "deleted" bit NOT NULL DEFAULT FALSE,
-  "tenant_id" bigint NOT NULL,
+  "tenant_id" bigint not null default  '0',
   PRIMARY KEY ("id")
 ) COMMENT '社交客户端表';