فهرست منبع

代码生成器:主子表,支持多个从字段 50%

zhijiantianya@gmail.com 1 سال پیش
والد
کامیت
2f6456e2b0
48فایلهای تغییر یافته به همراه916 افزوده شده و 147 حذف شده
  1. 4 0
      yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java
  2. 3 0
      yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java
  3. 2 1
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/vo/CodegenUpdateReqVO.java
  4. 90 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo02/InfraDemoStudentController.java
  5. 1 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo02/package-info.java
  6. 21 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo02/vo/InfraDemoStudentBaseVO.java
  7. 14 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo02/vo/InfraDemoStudentCreateReqVO.java
  8. 20 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo02/vo/InfraDemoStudentExcelVO.java
  9. 12 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo02/vo/InfraDemoStudentExportReqVO.java
  10. 14 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo02/vo/InfraDemoStudentPageReqVO.java
  11. 15 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo02/vo/InfraDemoStudentRespVO.java
  12. 18 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo02/vo/InfraDemoStudentUpdateReqVO.java
  13. 36 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/convert/demo02/InfraDemoStudentConvert.java
  14. 11 4
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/codegen/CodegenTableDO.java
  15. 37 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo02/InfraDemoStudentAddressDO.java
  16. 37 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo02/InfraDemoStudentContactDO.java
  17. 29 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo02/InfraDemoStudentDO.java
  18. 29 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo02/InfraDemoStudentAddressMapper.java
  19. 29 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo02/InfraDemoStudentContactMapper.java
  20. 31 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo02/InfraDemoStudentMapper.java
  21. 3 2
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenTemplateTypeEnum.java
  22. 5 4
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenServiceImpl.java
  23. 1 1
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenBuilder.java
  24. 52 20
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java
  25. 68 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo02/InfraDemoStudentService.java
  26. 107 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo02/InfraDemoStudentServiceImpl.java
  27. 0 10
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/controller.vm
  28. 9 3
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/baseVO.vm
  29. 2 0
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/dal/do_sub.vm
  30. 19 5
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/dal/mapper_sub.vm
  31. 26 8
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/service/service.vm
  32. 54 15
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/service/serviceImpl.vm
  33. 83 40
      yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngineTest.java
  34. 1 1
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserBaseVO
  35. 5 5
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserController
  36. 3 3
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserConvert
  37. 1 1
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserCreateReqVO
  38. 1 1
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserDO
  39. 1 1
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserExcelVO
  40. 1 1
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserExportReqVO
  41. 3 3
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserMapper
  42. 1 1
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserMapper_xml
  43. 1 1
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserPageReqVO
  44. 1 1
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserRespVO
  45. 3 3
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserService
  46. 6 6
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserServiceImpl
  47. 5 5
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserServiceImplTest
  48. 1 1
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserUpdateReqVO

+ 4 - 0
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java

@@ -136,4 +136,8 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
         return delete(new QueryWrapper<T>().eq(field, value));
     }
 
+    default int delete(SFunction<T, ?> field, Object value) {
+        return delete(new LambdaQueryWrapper<T>().eq(field, value));
+    }
+
 }

+ 3 - 0
yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java

@@ -54,4 +54,7 @@ public interface ErrorCodeConstants {
     ErrorCode DATA_SOURCE_CONFIG_NOT_EXISTS = new ErrorCode(1_001_007_000, "数据源配置不存在");
     ErrorCode DATA_SOURCE_CONFIG_NOT_OK = new ErrorCode(1_001_007_001, "数据源配置不正确,无法进行连接");
 
+    // ========== 数据源配置 1-001-107-000 ==========
+    ErrorCode DEMO_STUDENT_NOT_EXISTS = new ErrorCode(1_001_107_000, "学生不存在");
+
 }

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

@@ -44,9 +44,10 @@ public class CodegenUpdateReqVO {
                     || getParentMenuId() != null;
         }
 
+        // TODO 芋艿:这里有问题哈;
         @AssertTrue(message = "关联的子表与字段不能为空")
         public boolean isSubValid() {
-            return ObjectUtil.notEqual(getTemplateType(), CodegenTemplateTypeEnum.MASTER_SUB)
+            return ObjectUtil.notEqual(getTemplateType(), CodegenTemplateTypeEnum.SUB)
                     || (getSubTableId() != null && getSubColumnId() != null);
         }
 

+ 90 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo02/InfraDemoStudentController.java

@@ -0,0 +1,90 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo02;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
+import cn.iocoder.yudao.module.infra.controller.admin.demo02.vo.*;
+import cn.iocoder.yudao.module.infra.convert.demo02.InfraDemoStudentConvert;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo02.InfraDemoStudentDO;
+import cn.iocoder.yudao.module.infra.service.demo02.InfraDemoStudentService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
+
+@Tag(name = "管理后台 - 学生")
+@RestController
+@RequestMapping("/infra/demo-student")
+@Validated
+public class InfraDemoStudentController {
+
+    @Resource
+    private InfraDemoStudentService demoStudentService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建学生")
+    @PreAuthorize("@ss.hasPermission('infra:demo-student:create')")
+    public CommonResult<Long> createDemoStudent(@Valid @RequestBody InfraDemoStudentCreateReqVO createReqVO) {
+        return success(demoStudentService.createDemoStudent(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新学生")
+    @PreAuthorize("@ss.hasPermission('infra:demo-student:update')")
+    public CommonResult<Boolean> updateDemoStudent(@Valid @RequestBody InfraDemoStudentUpdateReqVO updateReqVO) {
+        demoStudentService.updateDemoStudent(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除学生")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('infra:demo-student:delete')")
+    public CommonResult<Boolean> deleteDemoStudent(@RequestParam("id") Long id) {
+        demoStudentService.deleteDemoStudent(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得学生")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('infra:demo-student:query')")
+    public CommonResult<InfraDemoStudentRespVO> getDemoStudent(@RequestParam("id") Long id) {
+        InfraDemoStudentDO demoStudent = demoStudentService.getDemoStudent(id);
+        return success(InfraDemoStudentConvert.INSTANCE.convert(demoStudent));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得学生分页")
+    @PreAuthorize("@ss.hasPermission('infra:demo-student:query')")
+    public CommonResult<PageResult<InfraDemoStudentRespVO>> getDemoStudentPage(@Valid InfraDemoStudentPageReqVO pageVO) {
+        PageResult<InfraDemoStudentDO> pageResult = demoStudentService.getDemoStudentPage(pageVO);
+        return success(InfraDemoStudentConvert.INSTANCE.convertPage(pageResult));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出学生 Excel")
+    @PreAuthorize("@ss.hasPermission('infra:demo-student:export')")
+    @OperateLog(type = EXPORT)
+    public void exportDemoStudentExcel(@Valid InfraDemoStudentExportReqVO exportReqVO,
+              HttpServletResponse response) throws IOException {
+        List<InfraDemoStudentDO> list = demoStudentService.getDemoStudentList(exportReqVO);
+        // 导出 Excel
+        List<InfraDemoStudentExcelVO> datas = InfraDemoStudentConvert.INSTANCE.convertList02(list);
+        ExcelUtils.write(response, "学生.xls", "数据", InfraDemoStudentExcelVO.class, datas);
+    }
+
+}

+ 1 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo02/package-info.java

@@ -0,0 +1 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo02;

+ 21 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo02/vo/InfraDemoStudentBaseVO.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo02.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import javax.validation.constraints.*;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo02.InfraDemoStudentContactDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo02.InfraDemoStudentAddressDO;
+
+/**
+ * 学生 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ */
+@Data
+public class InfraDemoStudentBaseVO {
+
+    private List<InfraDemoStudentContactDO> demoStudentContacts;
+
+    private InfraDemoStudentAddressDO demoStudentAddress;
+
+}

+ 14 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo02/vo/InfraDemoStudentCreateReqVO.java

@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo02.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import javax.validation.constraints.*;
+
+@Schema(description = "管理后台 - 学生创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class InfraDemoStudentCreateReqVO extends InfraDemoStudentBaseVO {
+
+}

+ 20 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo02/vo/InfraDemoStudentExcelVO.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo02.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+
+/**
+ * 学生 Excel VO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class InfraDemoStudentExcelVO {
+
+    @ExcelProperty("编号")
+    private Long id;
+
+}

+ 12 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo02/vo/InfraDemoStudentExportReqVO.java

@@ -0,0 +1,12 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo02.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+
+@Schema(description = "管理后台 - 学生 Excel 导出 Request VO,参数和 InfraDemoStudentPageReqVO 是一致的")
+@Data
+public class InfraDemoStudentExportReqVO {
+
+}

+ 14 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo02/vo/InfraDemoStudentPageReqVO.java

@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo02.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+
+@Schema(description = "管理后台 - 学生分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class InfraDemoStudentPageReqVO extends PageParam {
+
+}

+ 15 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo02/vo/InfraDemoStudentRespVO.java

@@ -0,0 +1,15 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo02.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+
+@Schema(description = "管理后台 - 学生 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class InfraDemoStudentRespVO extends InfraDemoStudentBaseVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long id;
+
+}

+ 18 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo02/vo/InfraDemoStudentUpdateReqVO.java

@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo02.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import javax.validation.constraints.*;
+
+@Schema(description = "管理后台 - 学生更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class InfraDemoStudentUpdateReqVO extends InfraDemoStudentBaseVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotNull(message = "编号不能为空")
+    private Long id;
+
+}

+ 36 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/convert/demo02/InfraDemoStudentConvert.java

@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.infra.convert.demo02;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.infra.controller.admin.demo02.vo.InfraDemoStudentCreateReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.demo02.vo.InfraDemoStudentExcelVO;
+import cn.iocoder.yudao.module.infra.controller.admin.demo02.vo.InfraDemoStudentRespVO;
+import cn.iocoder.yudao.module.infra.controller.admin.demo02.vo.InfraDemoStudentUpdateReqVO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo02.InfraDemoStudentDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+ * 学生 Convert
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface InfraDemoStudentConvert {
+
+    InfraDemoStudentConvert INSTANCE = Mappers.getMapper(InfraDemoStudentConvert.class);
+
+    InfraDemoStudentDO convert(InfraDemoStudentCreateReqVO bean);
+
+    InfraDemoStudentDO convert(InfraDemoStudentUpdateReqVO bean);
+
+    InfraDemoStudentRespVO convert(InfraDemoStudentDO bean);
+
+    List<InfraDemoStudentRespVO> convertList(List<InfraDemoStudentDO> list);
+
+    PageResult<InfraDemoStudentRespVO> convertPage(PageResult<InfraDemoStudentDO> page);
+
+    List<InfraDemoStudentExcelVO> convertList02(List<InfraDemoStudentDO> list);
+
+}

+ 11 - 4
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/codegen/CodegenTableDO.java

@@ -119,16 +119,23 @@ public class CodegenTableDO extends BaseDO {
     // ========== 主子表相关字段 ==========
 
     /**
-     * 子表的表编号
+     * 主表的编号
      *
      * 关联 {@link CodegenTableDO#getId()}
      */
-    private Long subTableId;
+    private Long masterTableId;
     /**
-     * 子表关联字段编号
+     * 【自己】子表关联主表的字段编号
      *
      * 关联 {@link CodegenColumnDO#getId()}
      */
-    private Long subColumnId;
+    private Long subJoinColumnId;
+    /**
+     * 主表与子表是否一对多
+     *
+     * true:一对多
+     * false:一对一
+     */
+    private Boolean subJoinMany;
 
 }

+ 37 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo02/InfraDemoStudentAddressDO.java

@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.infra.dal.dataobject.demo02;
+
+import lombok.*;
+import java.util.*;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * 学生地址 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("infra_demo_student_address")
+@KeySequence("infra_demo_student_address_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class InfraDemoStudentAddressDO extends BaseDO {
+
+    /**
+     * 编号
+     */
+    @TableId
+    private Long id;
+    /**
+     * 学生编号
+     */
+    private Long studentId;
+    /**
+     * 明细
+     */
+    private String detail;
+
+}

+ 37 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo02/InfraDemoStudentContactDO.java

@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.infra.dal.dataobject.demo02;
+
+import lombok.*;
+import java.util.*;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * 学生联系人 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("infra_demo_student_contact")
+@KeySequence("infra_demo_student_contact_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class InfraDemoStudentContactDO extends BaseDO {
+
+    /**
+     * 编号
+     */
+    @TableId
+    private Long id;
+    /**
+     * 学生编号
+     */
+    private Long studentId;
+    /**
+     * 名字
+     */
+    private String name;
+
+}

+ 29 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo02/InfraDemoStudentDO.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.infra.dal.dataobject.demo02;
+
+import lombok.*;
+import java.util.*;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * 学生 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("infra_demo_student")
+@KeySequence("infra_demo_student_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class InfraDemoStudentDO extends BaseDO {
+
+    /**
+     * 编号
+     */
+    @TableId
+    private Long id;
+
+}

+ 29 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo02/InfraDemoStudentAddressMapper.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.infra.dal.mysql.demo02;
+
+import java.util.*;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo02.InfraDemoStudentAddressDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 学生地址 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface InfraDemoStudentAddressMapper extends BaseMapperX<InfraDemoStudentAddressDO> {
+
+    default InfraDemoStudentAddressDO selectByStudentId(Long studentId) {
+        return selectOne(InfraDemoStudentAddressDO::getStudentId, studentId);
+    }
+
+    default List<InfraDemoStudentAddressDO> selectListByStudentId(List<Long> studentIds) {
+        return selectList(InfraDemoStudentAddressDO::getStudentId, studentIds);
+    }
+
+    default int deleteByStudentId(Long studentId) {
+        return delete(InfraDemoStudentAddressDO::getStudentId, studentId);
+    }
+
+}

+ 29 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo02/InfraDemoStudentContactMapper.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.infra.dal.mysql.demo02;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo02.InfraDemoStudentContactDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 学生联系人 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface InfraDemoStudentContactMapper extends BaseMapperX<InfraDemoStudentContactDO> {
+
+    default List<InfraDemoStudentContactDO> selectListByStudentId(Long studentId) {
+        return selectList(InfraDemoStudentContactDO::getStudentId, studentId);
+    }
+
+    default List<InfraDemoStudentContactDO> selectListByStudentId(List<Long> studentIds) {
+        return selectList(InfraDemoStudentContactDO::getStudentId, studentIds);
+    }
+
+    default int deleteByStudentId(Long studentId) {
+        return delete(InfraDemoStudentContactDO::getStudentId, studentId);
+    }
+
+}

+ 31 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo02/InfraDemoStudentMapper.java

@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.infra.dal.mysql.demo02;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.infra.controller.admin.demo02.vo.InfraDemoStudentExportReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.demo02.vo.InfraDemoStudentPageReqVO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo02.InfraDemoStudentDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 学生 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface InfraDemoStudentMapper extends BaseMapperX<InfraDemoStudentDO> {
+
+    default PageResult<InfraDemoStudentDO> selectPage(InfraDemoStudentPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<InfraDemoStudentDO>()
+                .orderByDesc(InfraDemoStudentDO::getId));
+    }
+
+    default List<InfraDemoStudentDO> selectList(InfraDemoStudentExportReqVO reqVO) {
+        return selectList(new LambdaQueryWrapperX<InfraDemoStudentDO>()
+                .orderByDesc(InfraDemoStudentDO::getId));
+    }
+
+}

+ 3 - 2
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenTemplateTypeEnum.java

@@ -12,9 +12,10 @@ import lombok.Getter;
 @Getter
 public enum CodegenTemplateTypeEnum {
 
-    CRUD(1), // 单表(增删改查)
+    ONE(1), // 单表(增删改查)
     TREE(2), // 树表(增删改查)
-    MASTER_SUB(3), // 主子表
+    MASTER(10), // 主子表 - 主表
+    SUB(11), // 主子表 - 子表
     ;
 
     /**

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

@@ -238,13 +238,14 @@ public class CodegenServiceImpl implements CodegenService {
         // 校验子表是否已经存在
         CodegenTableDO subTable = null;
         List<CodegenColumnDO> subColumns = null;
-        if (table.getSubTableId() != null) {
-            subTable = codegenTableMapper.selectById(table.getSubTableId());
-            subColumns = codegenColumnMapper.selectListByTableId(table.getSubTableId());
+        if (table.getMasterTableId() != null) {
+            subTable = codegenTableMapper.selectById(table.getMasterTableId());
+            subColumns = codegenColumnMapper.selectListByTableId(table.getMasterTableId());
         }
 
         // 执行生成
-        return codegenEngine.execute(table, columns, subTable, subColumns);
+//        return codegenEngine.execute(table, columns, subTable, subColumns);
+        return codegenEngine.execute(table, columns, null, null);
     }
 
     @Override

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

@@ -118,7 +118,7 @@ public class CodegenBuilder {
         table.setClassName(upperFirst(toCamelCase(subAfter(tableName, '_', false))));
         // 去除结尾的表,作为类描述
         table.setClassComment(StrUtil.removeSuffixIgnoreCase(table.getTableComment(), "表"));
-        table.setTemplateType(CodegenTemplateTypeEnum.CRUD.getType());
+        table.setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
     }
 
     public List<CodegenColumnDO> buildColumns(Long tableId, List<TableField> tableFields) {

+ 52 - 20
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.infra.service.codegen.inner;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.ObjUtil;
 import cn.hutool.core.util.StrUtil;
@@ -186,12 +187,12 @@ public class CodegenEngine {
      *
      * @param table 表定义
      * @param columns table 的字段定义数组
-     * @param subTable 子表定义,当且仅当主子表时使用
-     * @param subColumns subTable 的字段定义数组
+     * @param subTables 子表数组,当且仅当主子表时使用
+     * @param subColumnsList subTables 的字段定义数组
      * @return 生成的代码,key 是路径,value 是对应代码
      */
     public Map<String, String> execute(CodegenTableDO table, List<CodegenColumnDO> columns,
-                                       CodegenTableDO subTable, List<CodegenColumnDO> subColumns) {
+                                       List<CodegenTableDO> subTables, List<List<CodegenColumnDO>> subColumnsList) {
         // 创建 bindingMap
         Map<String, Object> bindingMap = new HashMap<>(globalBindingMap);
         bindingMap.put("table", table);
@@ -212,27 +213,51 @@ public class CodegenEngine {
         bindingMap.put("permissionPrefix", table.getModuleName() + ":" + simpleClassNameStrikeCase);
 
         // 特殊:主子表专属逻辑
-        if (subTable != null) {
+        if (CollUtil.isNotEmpty(subTables)) {
             // 创建 bindingMap
-            bindingMap.put("subTable", subTable);
-            bindingMap.put("subColumns", subColumns);
-            bindingMap.put("subColumn", CollectionUtils.findFirst(subColumns, // 关联的字段
-                    column -> Objects.equals(column.getId(), table.getSubColumnId())));
-            // className 相关
-            String subSimpleClassName = removePrefix(subTable.getClassName(), upperFirst(subTable.getModuleName()));
-            bindingMap.put("subSimpleClassName", subSimpleClassName);
-            bindingMap.put("subClassNameVar", lowerFirst(subSimpleClassName)); // 将 DictType 转换成 dictType,用于变量
+            bindingMap.put("subTables", subTables);
+            bindingMap.put("subColumnsList", subColumnsList);
+            List<CodegenColumnDO> subJoinColumns = new ArrayList<>();
+            List<String> subSimpleClassNames = new ArrayList<>();
+            List<String> subClassNameVars = new ArrayList<>();
+            for (int i = 0; i < subTables.size(); i++) {
+                CodegenTableDO subTable = subTables.get(i);
+                List<CodegenColumnDO> subColumns = subColumnsList.get(i);
+                subJoinColumns.add(CollectionUtils.findFirst(subColumns, // 关联的字段
+                        column -> Objects.equals(column.getId(), subTable.getSubJoinColumnId())));
+                // className 相关
+                String subSimpleClassName = removePrefix(subTable.getClassName(), upperFirst(subTable.getModuleName()));
+                subSimpleClassNames.add(subSimpleClassName);
+                subClassNameVars.add(lowerFirst(subSimpleClassName)); // 将 DictType 转换成 dictType,用于变量
+            }
+            bindingMap.put("subJoinColumns", subJoinColumns);
+            bindingMap.put("subSimpleClassNames", subSimpleClassNames);
+            bindingMap.put("subClassNameVars", subClassNameVars);
         }
 
         // 执行生成
         Map<String, String> templates = getTemplates(table.getTemplateType(), table.getFrontType());
         Map<String, String> result = Maps.newLinkedHashMapWithExpectedSize(templates.size()); // 有序
         templates.forEach((vmPath, filePath) -> {
-            filePath = formatFilePath(filePath, bindingMap);
-            String content = templateEngine.getTemplate(vmPath).render(bindingMap);
-            // 去除字段后面多余的 , 逗号
-            content = content.replaceAll(",\n}", "\n}").replaceAll(",\n  }", "\n  }");
-            result.put(filePath, content);
+            // 特殊:主子表专属逻辑
+            if (isSubTemplate(vmPath)) {
+                for (int i = 0; i < subTables.size(); i++) {
+                    bindingMap.put("subIndex", i);
+                    // TODO 芋艿:这块需要优化下逻辑
+                    String newFilePath = formatFilePath(filePath, bindingMap);
+                    String content = templateEngine.getTemplate(vmPath).render(bindingMap);
+                    // 去除字段后面多余的 , 逗号
+                    content = content.replaceAll(",\n}", "\n}").replaceAll(",\n  }", "\n  }");
+                    result.put(newFilePath, content);
+                    bindingMap.remove("subIndex");
+                }
+            } else {
+                filePath = formatFilePath(filePath, bindingMap);
+                String content = templateEngine.getTemplate(vmPath).render(bindingMap);
+                // 去除字段后面多余的 , 逗号
+                content = content.replaceAll(",\n}", "\n}").replaceAll(",\n  }", "\n  }");
+                result.put(filePath, content);
+            }
         });
         return result;
     }
@@ -242,7 +267,7 @@ public class CodegenEngine {
         templates.putAll(SERVER_TEMPLATES);
         templates.putAll(FRONT_TEMPLATES.row(frontType));
         // 特殊:主子表专属逻辑
-        if (ObjUtil.notEqual(templateType, CodegenTemplateTypeEnum.MASTER_SUB.getType())) {
+        if (ObjUtil.notEqual(templateType, CodegenTemplateTypeEnum.MASTER.getType())) {
             templates.remove(javaTemplatePath("dal/do_sub"));
             templates.remove(javaTemplatePath("dal/mapper_sub"));
         }
@@ -266,8 +291,11 @@ public class CodegenEngine {
         filePath = StrUtil.replace(filePath, "${table.businessName}", table.getBusinessName());
         filePath = StrUtil.replace(filePath, "${table.className}", table.getClassName());
         // 特殊:主子表专属逻辑
-        CodegenTableDO subTable = (CodegenTableDO) bindingMap.get("subTable");
-        if (subTable != null) {
+        Integer subIndex = (Integer) bindingMap.get("subIndex");
+        if (subIndex != null) {
+            CodegenTableDO subTable = ((List<CodegenTableDO>) bindingMap.get("subTables")).get(subIndex);
+            filePath = StrUtil.replace(filePath, "${subTable.moduleName}", subTable.getModuleName());
+            filePath = StrUtil.replace(filePath, "${subTable.businessName}", subTable.getBusinessName());
             filePath = StrUtil.replace(filePath, "${subTable.className}", subTable.getClassName());
         }
         return filePath;
@@ -336,4 +364,8 @@ public class CodegenEngine {
     private static String vue3VbenTemplatePath(String path) {
         return "codegen/vue3_vben/" + path + ".vm";
     }
+
+    private static boolean isSubTemplate(String path) {
+        return path.contains("_sub");
+    }
 }

+ 68 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo02/InfraDemoStudentService.java

@@ -0,0 +1,68 @@
+package cn.iocoder.yudao.module.infra.service.demo02;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.infra.controller.admin.demo02.vo.InfraDemoStudentCreateReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.demo02.vo.InfraDemoStudentExportReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.demo02.vo.InfraDemoStudentPageReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.demo02.vo.InfraDemoStudentUpdateReqVO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo02.InfraDemoStudentDO;
+
+import javax.validation.Valid;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 学生 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface InfraDemoStudentService {
+
+    /**
+     * 创建学生
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    Long createDemoStudent(@Valid InfraDemoStudentCreateReqVO createReqVO);
+
+    /**
+     * 更新学生
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateDemoStudent(@Valid InfraDemoStudentUpdateReqVO updateReqVO);
+
+    /**
+     * 删除学生
+     *
+     * @param id 编号
+     */
+    void deleteDemoStudent(Long id);
+
+    /**
+     * 获得学生
+     *
+     * @param id 编号
+     * @return 学生
+     */
+    InfraDemoStudentDO getDemoStudent(Long id);
+
+    /**
+     * 获得学生分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 学生分页
+     */
+    PageResult<InfraDemoStudentDO> getDemoStudentPage(InfraDemoStudentPageReqVO pageReqVO);
+
+    /**
+     * 获得学生列表, 用于 Excel 导出
+     *
+     * @param exportReqVO 查询条件
+     * @return 学生列表
+     */
+    List<InfraDemoStudentDO> getDemoStudentList(InfraDemoStudentExportReqVO exportReqVO);
+
+
+}

+ 107 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo02/InfraDemoStudentServiceImpl.java

@@ -0,0 +1,107 @@
+package cn.iocoder.yudao.module.infra.service.demo02;
+
+import org.springframework.stereotype.Service;
+import javax.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import cn.iocoder.yudao.module.infra.controller.admin.demo02.vo.*;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo02.InfraDemoStudentDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+import cn.iocoder.yudao.module.infra.convert.demo02.InfraDemoStudentConvert;
+import cn.iocoder.yudao.module.infra.dal.mysql.demo02.InfraDemoStudentMapper;
+import cn.iocoder.yudao.module.infra.dal.mysql.demo02.InfraDemoStudentContactMapper;
+import cn.iocoder.yudao.module.infra.dal.mysql.demo02.InfraDemoStudentAddressMapper;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.ListUtil;
+
+/**
+ * 学生 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class InfraDemoStudentServiceImpl implements InfraDemoStudentService {
+
+    @Resource
+    private InfraDemoStudentMapper demoStudentMapper;
+    @Resource
+    private InfraDemoStudentContactMapper demoStudentContactMapper;
+    @Resource
+    private InfraDemoStudentAddressMapper demoStudentAddressMapper;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createDemoStudent(InfraDemoStudentCreateReqVO createReqVO) {
+        // 插入
+        InfraDemoStudentDO demoStudent = InfraDemoStudentConvert.INSTANCE.convert(createReqVO);
+        demoStudentMapper.insert(demoStudent);
+        // 插入子表(学生联系人)
+        createReqVO.getDemoStudentContacts().forEach(o -> o.setStudentId(demoStudent.getId()));
+        demoStudentContactMapper.insertBatch(createReqVO.getDemoStudentContacts());
+        // 插入子表(学生地址)
+        createReqVO.getDemoStudentAddress().setStudentId(demoStudent.getId());
+        demoStudentAddressMapper.insert(createReqVO.getDemoStudentAddress());
+        // 返回
+        return demoStudent.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateDemoStudent(InfraDemoStudentUpdateReqVO updateReqVO) {
+        // 校验存在
+        validateDemoStudentExists(updateReqVO.getId());
+        // 更新
+        InfraDemoStudentDO updateObj = InfraDemoStudentConvert.INSTANCE.convert(updateReqVO);
+        demoStudentMapper.updateById(updateObj);
+        // 更新子表(学生联系人)
+        demoStudentContactMapper.deleteByStudentId(updateReqVO.getId());
+        updateReqVO.getDemoStudentContacts().forEach(o -> o.setStudentId(updateReqVO.getId()));
+        demoStudentContactMapper.insertBatch(updateReqVO.getDemoStudentContacts());
+        // 更新子表(学生地址)
+        updateReqVO.getDemoStudentAddress().setStudentId(updateReqVO.getId());
+        demoStudentAddressMapper.updateById(updateReqVO.getDemoStudentAddress());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteDemoStudent(Long id) {
+        // 校验存在
+        validateDemoStudentExists(id);
+        // 删除
+        demoStudentMapper.deleteById(id);
+        // 删除子表(学生联系人)
+        demoStudentContactMapper.deleteByStudentId(id);
+        // 删除子表(学生地址)
+        demoStudentAddressMapper.deleteByStudentId(id);
+    }
+
+    private void validateDemoStudentExists(Long id) {
+        if (demoStudentMapper.selectById(id) == null) {
+            throw exception(DEMO_STUDENT_NOT_EXISTS);
+        }
+    }
+
+    @Override
+    public InfraDemoStudentDO getDemoStudent(Long id) {
+        return demoStudentMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<InfraDemoStudentDO> getDemoStudentPage(InfraDemoStudentPageReqVO pageReqVO) {
+        return demoStudentMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public List<InfraDemoStudentDO> getDemoStudentList(InfraDemoStudentExportReqVO exportReqVO) {
+        return demoStudentMapper.selectList(exportReqVO);
+    }
+
+}

+ 0 - 10
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/controller.vm

@@ -76,16 +76,6 @@ public class ${sceneEnum.prefixClass}${table.className}Controller {
         return success(${table.className}Convert.INSTANCE.convert(${classNameVar}));
     }
 
-    @GetMapping("/list")
-    @Operation(summary = "获得${table.classComment}列表")
-    @Parameter(name = "ids", description = "编号列表", required = true, example = "1024,2048")
-#if ($sceneEnum.scene == 1)    @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")#end
-
-    public CommonResult<List<${sceneEnum.prefixClass}${table.className}RespVO>> get${simpleClassName}List(@RequestParam("ids") Collection<${primaryColumn.javaType}> ids) {
-        List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}List(ids);
-        return success(${table.className}Convert.INSTANCE.convertList(list));
-    }
-
     @GetMapping("/page")
     @Operation(summary = "获得${table.classComment}分页")
 #if ($sceneEnum.scene == 1)    @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")#end

+ 9 - 3
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/baseVO.vm

@@ -23,7 +23,7 @@ import static ${DateUtilsClassName}.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
 #end
 #end
 ## 特殊:主子表专属逻辑
-#if ( $subTable )
+#foreach ($subTable in $subTables)
 import ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName}.${subTable.className}DO;
 #end
 
@@ -41,8 +41,14 @@ public class ${sceneEnum.prefixClass}${table.className}BaseVO {
 #end
 #end
 ## 特殊:主子表专属逻辑
-#if ( $subTable )
-    private List<${subTable.className}DO> ${subClassNameVar}s;
+#foreach ($subTable in $subTables)
+#set ($index = $foreach.count - 1)
+    #if ( $subTable.subJoinMany)
+    private List<${subTable.className}DO> ${subClassNameVars.get($index)}s;
 
+    #else
+    private ${subTable.className}DO ${subClassNameVars.get($index)};
+
+    #end
 #end
 }

+ 2 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/dal/do_sub.vm

@@ -1,3 +1,5 @@
+#set ($subTable = $subTables.get($subIndex))##当前表
+#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组
 package ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName};
 
 import lombok.*;

+ 19 - 5
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/dal/mapper_sub.vm

@@ -1,3 +1,7 @@
+#set ($subTable = $subTables.get($subIndex))##当前表
+#set ($subColumns = $subJoinColumnsList.get($subIndex))##当前字段数组
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
 package ${basePackage}.module.${subTable.moduleName}.dal.mysql.${subTable.businessName};
 
 import java.util.*;
@@ -6,7 +10,6 @@ import ${BaseMapperClassName};
 import ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName}.${subTable.className}DO;
 import org.apache.ibatis.annotations.Mapper;
 
-#set ($SubColumnName = $subColumn.javaField.substring(0,1).toUpperCase() + ${subColumn.javaField.substring(1)})##首字母大写
 /**
  * ${subTable.classComment} Mapper
  *
@@ -15,12 +18,23 @@ import org.apache.ibatis.annotations.Mapper;
 @Mapper
 public interface ${subTable.className}Mapper extends BaseMapperX<${subTable.className}DO> {
 
-    default List<${subTable.className}DO> selectListBy${SubColumnName}(${subColumn.javaType} ${subColumn.javaField}) {
-        return selectList(${subTable.className}DO::get${SubColumnName}, ${subColumn.javaField});
+#if ( $subTable.subJoinMany)
+    default List<${subTable.className}DO> selectListBy${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField}) {
+        return selectList(${subTable.className}DO::get${SubJoinColumnName}, ${subJoinColumn.javaField});
     }
 
-    default List<${subTable.className}DO> selectListBy${SubColumnName}(List<${subColumn.javaType}> ${subColumn.javaField}s) {
-        return selectList(${subTable.className}DO::get${SubColumnName}, ${subColumn.javaField}s);
+#else
+    default ${subTable.className}DO selectBy${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField}) {
+        return selectOne(${subTable.className}DO::get${SubJoinColumnName}, ${subJoinColumn.javaField});
+    }
+
+#end
+    default List<${subTable.className}DO> selectListBy${SubJoinColumnName}(List<${subJoinColumn.javaType}> ${subJoinColumn.javaField}s) {
+        return selectList(${subTable.className}DO::get${SubJoinColumnName}, ${subJoinColumn.javaField}s);
+    }
+
+    default int deleteBy${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField}) {
+        return delete(${subTable.className}DO::get${SubJoinColumnName}, ${subJoinColumn.javaField});
     }
 
 }

+ 26 - 8
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/service/service.vm

@@ -43,14 +43,6 @@ public interface ${table.className}Service {
      */
     ${table.className}DO get${simpleClassName}(${primaryColumn.javaType} id);
 
-    /**
-     * 获得${table.classComment}列表
-     *
-     * @param ids 编号
-     * @return ${table.classComment}列表
-     */
-    List<${table.className}DO> get${simpleClassName}List(Collection<${primaryColumn.javaType}> ids);
-
     /**
      * 获得${table.classComment}分页
      *
@@ -67,4 +59,30 @@ public interface ${table.className}Service {
      */
     List<${table.className}DO> get${simpleClassName}List(${sceneEnum.prefixClass}${table.className}ExportReqVO exportReqVO);
 
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+#set ($index = $foreach.count - 1)
+#set ($subSimpleClassName = $subSimpleClassNames.get($index))
+#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+    #if ( $subTable.subJoinMany )
+    /**
+     * 获得${subTable.classComment}列表
+     *
+     * @param ${subJoinColumn.javaField} ${subJoinColumn.columnComment}
+     * @return ${subTable.classComment}列表
+     */
+    List<${subTable.className}DO> get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField});
+
+    #else
+    /**
+     * 获得${subTable.classComment}
+     *
+     * @param ${subJoinColumn.javaField} ${subJoinColumn.columnComment}
+     * @return ${subTable.classComment}
+     */
+    ${subTable.className}DO get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField});
+
+    #end
+#end
 }

+ 54 - 15
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/service/serviceImpl.vm

@@ -3,6 +3,7 @@ package ${basePackage}.module.${table.moduleName}.service.${table.businessName};
 import org.springframework.stereotype.Service;
 import javax.annotation.Resource;
 import org.springframework.validation.annotation.Validated;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.util.*;
 import ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*;
@@ -16,8 +17,9 @@ import ${PageResultClassName};
 import ${basePackage}.module.${table.moduleName}.convert.${table.businessName}.${table.className}Convert;
 import ${basePackage}.module.${table.moduleName}.dal.mysql.${table.businessName}.${table.className}Mapper;
 ## 特殊:主子表专属逻辑
-#if( $subTable )
-import ${basePackage}.module.${subTable.moduleName}.dal.mysql.${subTable.businessName}.${subClassNameVar}Mapper;
+#foreach ($subTable in $subTables)
+#set ($index = $foreach.count - 1)
+import ${basePackage}.module.${subTable.moduleName}.dal.mysql.${subTable.businessName}.${subTable.className}Mapper;
 #end
 
 import static ${ServiceExceptionUtilClassName}.exception;
@@ -38,40 +40,85 @@ public class ${table.className}ServiceImpl implements ${table.className}Service
     @Resource
     private ${table.className}Mapper ${classNameVar}Mapper;
 ## 特殊:主子表专属逻辑
-#if( $subTable )
+#foreach ($subTable in $subTables)
+#set ($index = $foreach.count - 1)
     @Resource
-    private ${subTable.className}Mapper ${subClassNameVar}Mapper;
+    private ${subTable.className}Mapper ${subClassNameVars.get($index)}Mapper;
 #end
 
     @Override
+## 特殊:主子表专属逻辑
+#if ( $subTables.size() > 0)
+    @Transactional(rollbackFor = Exception.class)
+#end
     public ${primaryColumn.javaType} create${simpleClassName}(${sceneEnum.prefixClass}${table.className}CreateReqVO createReqVO) {
         // 插入
         ${table.className}DO ${classNameVar} = ${table.className}Convert.INSTANCE.convert(createReqVO);
         ${classNameVar}Mapper.insert(${classNameVar});
 ## 特殊:主子表专属逻辑
-#if( $subTable )
-        // 插入子表
-        ${subClassNameVar}Mapper.insertBatch(createReqVO.get${subSimpleClassName}s);
+#foreach ($subTable in $subTables)
+#set ($index = $foreach.count - 1)
+#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+        // 插入子表($subTable.classComment)
+    #if ( $subTable.subJoinMany)
+        createReqVO.get${subSimpleClassNames.get($index)}s().forEach(o -> o.set$SubJoinColumnName(${classNameVar}.getId()));
+        ${subClassNameVars.get($index)}Mapper.insertBatch(createReqVO.get${subSimpleClassNames.get($index)}s());
+    #else
+        createReqVO.get${subSimpleClassNames.get($index)}().set$SubJoinColumnName(${classNameVar}.getId());
+        ${subClassNameVars.get($index)}Mapper.insert(createReqVO.get${subSimpleClassNames.get($index)}());
+    #end
 #end
         // 返回
         return ${classNameVar}.getId();
     }
 
     @Override
+## 特殊:主子表专属逻辑
+#if ( $subTables.size() > 0)
+    @Transactional(rollbackFor = Exception.class)
+#end
     public void update${simpleClassName}(${sceneEnum.prefixClass}${table.className}UpdateReqVO updateReqVO) {
         // 校验存在
         validate${simpleClassName}Exists(updateReqVO.getId());
         // 更新
         ${table.className}DO updateObj = ${table.className}Convert.INSTANCE.convert(updateReqVO);
         ${classNameVar}Mapper.updateById(updateObj);
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+#set ($index = $foreach.count - 1)
+#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+        // 更新子表($subTable.classComment)
+    #if ( $subTable.subJoinMany)
+        ${subClassNameVars.get($index)}Mapper.deleteBy${SubJoinColumnName}(updateReqVO.getId());
+        updateReqVO.get${subSimpleClassNames.get($index)}s().forEach(o -> o.set$SubJoinColumnName(updateReqVO.getId()));
+        ${subClassNameVars.get($index)}Mapper.insertBatch(updateReqVO.get${subSimpleClassNames.get($index)}s());
+    #else
+        updateReqVO.get${subSimpleClassNames.get($index)}().set$SubJoinColumnName(updateReqVO.getId());
+        ${subClassNameVars.get($index)}Mapper.updateById(updateReqVO.get${subSimpleClassNames.get($index)}());
+    #end
+#end
     }
 
     @Override
+## 特殊:主子表专属逻辑
+#if ( $subTables.size() > 0)
+    @Transactional(rollbackFor = Exception.class)
+#end
     public void delete${simpleClassName}(${primaryColumn.javaType} id) {
         // 校验存在
         validate${simpleClassName}Exists(id);
         // 删除
         ${classNameVar}Mapper.deleteById(id);
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+#set ($index = $foreach.count - 1)
+#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+        // 删除子表($subTable.classComment)
+        ${subClassNameVars.get($index)}Mapper.deleteBy${SubJoinColumnName}(id);
+#end
     }
 
     private void validate${simpleClassName}Exists(${primaryColumn.javaType} id) {
@@ -85,14 +132,6 @@ public class ${table.className}ServiceImpl implements ${table.className}Service
         return ${classNameVar}Mapper.selectById(id);
     }
 
-    @Override
-    public List<${table.className}DO> get${simpleClassName}List(Collection<${primaryColumn.javaType}> ids) {
-        if (CollUtil.isEmpty(ids)) {
-            return ListUtil.empty();
-        }
-        return ${classNameVar}Mapper.selectBatchIds(ids);
-    }
-
     @Override
     public PageResult<${table.className}DO> get${simpleClassName}Page(${sceneEnum.prefixClass}${table.className}PageReqVO pageReqVO) {
         return ${classNameVar}Mapper.selectPage(pageReqVO);

+ 83 - 40
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngineTest.java

@@ -31,7 +31,7 @@ public class CodegenEngineTest extends BaseMockitoUnitTest {
 
     @Spy
     private CodegenProperties codegenProperties = new CodegenProperties()
-            .setBasePackage("cn.iocoder.yudao.module");
+            .setBasePackage("cn.iocoder.yudao");
 
     @BeforeEach
     public void setUp() {
@@ -45,7 +45,7 @@ public class CodegenEngineTest extends BaseMockitoUnitTest {
                 .setTableName("system_user").setTableComment("用户表")
                 .setModuleName("system").setBusinessName("user").setClassName("SystemUser")
                 .setClassComment("用户").setAuthor("芋道源码")
-                .setTemplateType(CodegenTemplateTypeEnum.CRUD.getType())
+                .setTemplateType(CodegenTemplateTypeEnum.ONE.getType())
                 .setFrontType(CodegenFrontTypeEnum.VUE3.getType())
                 .setParentMenuId(10L);
         CodegenColumnDO idColumn = new CodegenColumnDO().setColumnName("id").setDataType(JdbcType.BIGINT.name())
@@ -125,32 +125,32 @@ public class CodegenEngineTest extends BaseMockitoUnitTest {
         for (String vo : new String[]{"SystemUserBaseVO", "SystemUserCreateReqVO", "SystemUserUpdateReqVO", "SystemUserRespVO",
                 "SystemUserPageReqVO", "SystemUserExportReqVO", "SystemUserExcelVO"}) {
             assertPathContentEquals("vue3_crud/java/" + vo,
-                    result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/module/system/controller/admin/user/vo/" + vo + ".java");
+                    result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/" + vo + ".java");
         }
         // 断言 controller 类
         assertPathContentEquals("vue3_crud/java/SystemUserController",
-                result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/module/system/controller/admin/user/SystemUserController.java");
+                result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/SystemUserController.java");
         // 断言 service 类
         assertPathContentEquals("vue3_crud/java/SystemUserService",
-                result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/module/system/service/user/SystemUserService.java");
+                result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/SystemUserService.java");
         assertPathContentEquals("vue3_crud/java/SystemUserServiceImpl",
-                result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/module/system/service/user/SystemUserServiceImpl.java");
+                result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/SystemUserServiceImpl.java");
         // 断言 convert 类
         assertPathContentEquals("vue3_crud/java/SystemUserConvert",
-                result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/module/system/convert/user/SystemUserConvert.java");
+                result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/user/SystemUserConvert.java");
         // 断言 enums 类
         assertPathContentEquals("vue3_crud/java/ErrorCodeConstants",
-                result, "yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/module/system/enums/ErrorCodeConstants_手动操作.java");
+                result, "yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants_手动操作.java");
         // 断言 dal 类
         assertPathContentEquals("vue3_crud/java/SystemUserDO",
-                result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/module/system/dal/dataobject/user/SystemUserDO.java");
+                result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/user/SystemUserDO.java");
         assertPathContentEquals("vue3_crud/java/SystemUserMapper",
-                result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/module/system/dal/mysql/user/SystemUserMapper.java");
+                result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/user/SystemUserMapper.java");
         assertPathContentEquals("vue3_crud/java/SystemUserMapper_xml",
                 result, "yudao-module-system/yudao-module-system-biz/src/main/resources/mapper/user/SystemUserMapper.xml");
         // 断言 test 类
         assertPathContentEquals("vue3_crud/java/SystemUserServiceImplTest",
-                result, "yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/module/system/service/user/SystemUserServiceImplTest.java");
+                result, "yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/user/SystemUserServiceImplTest.java");
         // 断言 sql 语句
         assertPathContentEquals("vue3_crud/sql/h2",
                 result, "sql/h2.sql");
@@ -170,73 +170,116 @@ public class CodegenEngineTest extends BaseMockitoUnitTest {
         // 准备请求参数
         // 主表
         CodegenTableDO table = new CodegenTableDO().setScene(CodegenSceneEnum.ADMIN.getScene())
-                .setTableName("system_user").setTableComment("用户表")
-                .setModuleName("system").setBusinessName("user").setClassName("SystemUser")
-                .setClassComment("用户").setAuthor("芋道源码")
-                .setTemplateType(CodegenTemplateTypeEnum.MASTER_SUB.getType()).setSubColumnId(100L)
-                .setFrontType(CodegenFrontTypeEnum.VUE3.getType())
-                .setParentMenuId(10L);
+                .setTableName("infra_demo_student").setTableComment("学生表")
+                .setModuleName("infra").setBusinessName("demo02").setClassName("InfraDemoStudent")
+                .setClassComment("学生").setAuthor("芋道源码")
+                .setTemplateType(CodegenTemplateTypeEnum.MASTER.getType())
+                .setFrontType(CodegenFrontTypeEnum.VUE3.getType());
         CodegenColumnDO idColumn = new CodegenColumnDO().setColumnName("id").setDataType(JdbcType.BIGINT.name())
                 .setColumnComment("编号").setNullable(false).setPrimaryKey(true).setAutoIncrement(true)
                 .setOrdinalPosition(1).setJavaType("Long").setJavaField("id").setExample("1024")
                 .setCreateOperation(false).setUpdateOperation(true).setListOperation(false)
                 .setListOperationResult(true);
+
         List<CodegenColumnDO> columns = Collections.singletonList(idColumn);
-        // 子表
-        CodegenTableDO subTable = new CodegenTableDO().setScene(CodegenSceneEnum.ADMIN.getScene())
-                .setTableName("system_user_contact").setTableComment("用户联系人表")
-                .setModuleName("system").setBusinessName("user").setClassName("SystemUserContact")
-                .setClassComment("用户联系人").setAuthor("芋道源码")
-                .setTemplateType(CodegenTemplateTypeEnum.CRUD.getType())
-                .setFrontType(CodegenFrontTypeEnum.VUE3.getType());
-        CodegenColumnDO subIdColumn = new CodegenColumnDO().setColumnName("id").setDataType(JdbcType.BIGINT.name())
+        // 子表(联系人)
+        CodegenTableDO contactTable = new CodegenTableDO().setScene(CodegenSceneEnum.ADMIN.getScene())
+                .setTableName("infra_demo_student_contact").setTableComment("学生联系人表")
+                .setModuleName("infra").setBusinessName("demo02").setClassName("InfraDemoStudentContact")
+                .setClassComment("学生联系人").setAuthor("芋道源码")
+                .setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
+                .setFrontType(CodegenFrontTypeEnum.VUE3.getType())
+                .setSubJoinColumnId(100L).setSubJoinMany(true);
+        CodegenColumnDO contactIdColumn = new CodegenColumnDO().setColumnName("id").setDataType(JdbcType.BIGINT.name())
                 .setColumnComment("编号").setNullable(false).setPrimaryKey(true).setAutoIncrement(true)
                 .setOrdinalPosition(1).setJavaType("Long").setJavaField("id").setExample("1024")
                 .setCreateOperation(false).setUpdateOperation(true).setListOperation(false)
                 .setListOperationResult(true);
-        CodegenColumnDO userIdColumn = new CodegenColumnDO().setColumnName("user_id").setDataType(JdbcType.BIGINT.name())
-                .setColumnComment("用户编号").setNullable(false).setPrimaryKey(false)
-                .setOrdinalPosition(2).setJavaType("Long").setJavaField("userId").setExample("2048")
+        CodegenColumnDO contactStudentIdColumn = new CodegenColumnDO().setColumnName("student_id").setDataType(JdbcType.BIGINT.name())
+                .setColumnComment("学生编号").setNullable(false).setPrimaryKey(false)
+                .setOrdinalPosition(2).setJavaType("Long").setJavaField("studentId").setExample("2048")
                 .setCreateOperation(false).setUpdateOperation(true).setListOperation(false)
                 .setListOperationResult(true)
                 .setId(100L);
-        List<CodegenColumnDO> subColumns = Arrays.asList(subIdColumn, userIdColumn);
+        CodegenColumnDO contactNameColumn = new CodegenColumnDO().setColumnName("name").setDataType(JdbcType.VARCHAR.name())
+                .setColumnComment("名字").setNullable(false).setPrimaryKey(false)
+                .setOrdinalPosition(3).setJavaType("String").setJavaField("name").setExample("芋头")
+                .setCreateOperation(true).setUpdateOperation(true).setListOperation(true)
+                .setListOperationCondition(CodegenColumnListConditionEnum.LIKE.getCondition()).setListOperationResult(true)
+                .setHtmlType(CodegenColumnHtmlTypeEnum.INPUT.getType());
+        List<CodegenColumnDO> contactColumns = Arrays.asList(contactIdColumn, contactStudentIdColumn, contactNameColumn);
+        // 子表(地址)
+        CodegenTableDO addressTable = new CodegenTableDO().setScene(CodegenSceneEnum.ADMIN.getScene())
+                .setTableName("infra_demo_student_address").setTableComment("学生地址表")
+                .setModuleName("infra").setBusinessName("demo02").setClassName("InfraDemoStudentAddress")
+                .setClassComment("学生地址").setAuthor("芋道源码")
+                .setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
+                .setFrontType(CodegenFrontTypeEnum.VUE3.getType())
+                .setSubJoinColumnId(200L).setSubJoinMany(false);
+        CodegenColumnDO addressIdColumn = new CodegenColumnDO().setColumnName("id").setDataType(JdbcType.BIGINT.name())
+                .setColumnComment("编号").setNullable(false).setPrimaryKey(true).setAutoIncrement(true)
+                .setOrdinalPosition(1).setJavaType("Long").setJavaField("id").setExample("1024")
+                .setCreateOperation(false).setUpdateOperation(true).setListOperation(false)
+                .setListOperationResult(true);
+        CodegenColumnDO addressStudentColumn = new CodegenColumnDO().setColumnName("student_id").setDataType(JdbcType.BIGINT.name())
+                .setColumnComment("学生编号").setNullable(false).setPrimaryKey(false)
+                .setOrdinalPosition(2).setJavaType("Long").setJavaField("studentId").setExample("2048")
+                .setCreateOperation(false).setUpdateOperation(true).setListOperation(false)
+                .setListOperationResult(true)
+                .setId(200L);
+        CodegenColumnDO addressDetailColumn = new CodegenColumnDO().setColumnName("detail").setDataType(JdbcType.VARCHAR.name())
+                .setColumnComment("明细").setNullable(false).setPrimaryKey(false)
+                .setOrdinalPosition(3).setJavaType("String").setJavaField("detail").setExample("码头路 88 号")
+                .setCreateOperation(true).setUpdateOperation(true).setListOperation(true)
+                .setListOperationCondition(CodegenColumnListConditionEnum.LIKE.getCondition()).setListOperationResult(true)
+                .setHtmlType(CodegenColumnHtmlTypeEnum.INPUT.getType());
+        List<CodegenColumnDO> addressColumns = Arrays.asList(addressIdColumn, addressStudentColumn, addressDetailColumn);
 
         // 调用
-        Map<String, String> result = codegenEngine.execute(table, columns, subTable, subColumns);
+        Map<String, String> result = codegenEngine.execute(table, columns,
+                Arrays.asList(contactTable, addressTable), Arrays.asList(contactColumns, addressColumns));
 
         // 断言
-        assertEquals(23, result.size());
+        assertEquals(25, result.size());
+
+        for (Map.Entry<String, String> entry : result.entrySet()) {
+            System.out.println(entry.getKey());
+            System.out.println(entry.getValue());
+            System.out.println("");
+            System.out.println("");
+            System.out.println("");
+        }
+
 //        // 断言 vo 类
 //        for (String vo : new String[]{"SystemUserBaseVO", "SystemUserCreateReqVO", "SystemUserUpdateReqVO", "SystemUserRespVO",
 //                "SystemUserPageReqVO", "SystemUserExportReqVO", "SystemUserExcelVO"}) {
 //            assertPathContentEquals("vue3_crud/java/" + vo,
-//                    result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/module/system/controller/admin/user/vo/" + vo + ".java");
+//                    result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/" + vo + ".java");
 //        }
 //        // 断言 controller 类
 //        assertPathContentEquals("vue3_crud/java/SystemUserController",
-//                result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/module/system/controller/admin/user/SystemUserController.java");
+//                result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/SystemUserController.java");
 //        // 断言 service 类
 //        assertPathContentEquals("vue3_crud/java/SystemUserService",
-//                result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/module/system/service/user/SystemUserService.java");
+//                result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/SystemUserService.java");
 //        assertPathContentEquals("vue3_crud/java/SystemUserServiceImpl",
-//                result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/module/system/service/user/SystemUserServiceImpl.java");
+//                result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/SystemUserServiceImpl.java");
 //        // 断言 convert 类
 //        assertPathContentEquals("vue3_crud/java/SystemUserConvert",
-//                result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/module/system/convert/user/SystemUserConvert.java");
+//                result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/user/SystemUserConvert.java");
 //        // 断言 enums 类
 //        assertPathContentEquals("vue3_crud/java/ErrorCodeConstants",
-//                result, "yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/module/system/enums/ErrorCodeConstants_手动操作.java");
+//                result, "yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants_手动操作.java");
 //        // 断言 dal 类
 //        assertPathContentEquals("vue3_crud/java/SystemUserDO",
-//                result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/module/system/dal/dataobject/user/SystemUserDO.java");
+//                result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/user/SystemUserDO.java");
 //        assertPathContentEquals("vue3_crud/java/SystemUserMapper",
-//                result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/module/system/dal/mysql/user/SystemUserMapper.java");
+//                result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/user/SystemUserMapper.java");
 //        assertPathContentEquals("vue3_crud/java/SystemUserMapper_xml",
 //                result, "yudao-module-system/yudao-module-system-biz/src/main/resources/mapper/user/SystemUserMapper.xml");
 //        // 断言 test 类
 //        assertPathContentEquals("vue3_crud/java/SystemUserServiceImplTest",
-//                result, "yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/module/system/service/user/SystemUserServiceImplTest.java");
+//                result, "yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/user/SystemUserServiceImplTest.java");
 //        // 断言 sql 语句
 //        assertPathContentEquals("vue3_crud/sql/h2",
 //                result, "sql/h2.sql");

+ 1 - 1
yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserBaseVO

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.module.system.controller.admin.user.vo;
+package cn.iocoder.yudao.module.system.controller.admin.user.vo;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.*;

+ 5 - 5
yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserController

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.module.system.controller.admin.user;
+package cn.iocoder.yudao.module.system.controller.admin.user;
 
 import org.springframework.web.bind.annotation.*;
 import javax.annotation.Resource;
@@ -23,10 +23,10 @@ import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.*;
 
-import cn.iocoder.yudao.module.module.system.controller.admin.user.vo.*;
-import cn.iocoder.yudao.module.module.system.dal.dataobject.user.SystemUserDO;
-import cn.iocoder.yudao.module.module.system.convert.user.SystemUserConvert;
-import cn.iocoder.yudao.module.module.system.service.user.SystemUserService;
+import cn.iocoder.yudao.module.system.controller.admin.user.vo.*;
+import cn.iocoder.yudao.module.system.dal.dataobject.user.SystemUserDO;
+import cn.iocoder.yudao.module.system.convert.user.SystemUserConvert;
+import cn.iocoder.yudao.module.system.service.user.SystemUserService;
 
 @Tag(name = "管理后台 - 用户")
 @RestController

+ 3 - 3
yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserConvert

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.module.system.convert.user;
+package cn.iocoder.yudao.module.system.convert.user;
 
 import java.util.*;
 
@@ -6,8 +6,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 
 import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;
-import cn.iocoder.yudao.module.module.system.controller.admin.user.vo.*;
-import cn.iocoder.yudao.module.module.system.dal.dataobject.user.SystemUserDO;
+import cn.iocoder.yudao.module.system.controller.admin.user.vo.*;
+import cn.iocoder.yudao.module.system.dal.dataobject.user.SystemUserDO;
 
 /**
  * 用户 Convert

+ 1 - 1
yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserCreateReqVO

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.module.system.controller.admin.user.vo;
+package cn.iocoder.yudao.module.system.controller.admin.user.vo;
 
 import lombok.*;
 import java.util.*;

+ 1 - 1
yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserDO

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.module.system.dal.dataobject.user;
+package cn.iocoder.yudao.module.system.dal.dataobject.user;
 
 import lombok.*;
 import java.util.*;

+ 1 - 1
yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserExcelVO

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.module.system.controller.admin.user.vo;
+package cn.iocoder.yudao.module.system.controller.admin.user.vo;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.*;

+ 1 - 1
yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserExportReqVO

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.module.system.controller.admin.user.vo;
+package cn.iocoder.yudao.module.system.controller.admin.user.vo;
 
 import lombok.*;
 import java.util.*;

+ 3 - 3
yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserMapper

@@ -1,13 +1,13 @@
-package cn.iocoder.yudao.module.module.system.dal.mysql.user;
+package cn.iocoder.yudao.module.system.dal.mysql.user;
 
 import java.util.*;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
-import cn.iocoder.yudao.module.module.system.dal.dataobject.user.SystemUserDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.user.SystemUserDO;
 import org.apache.ibatis.annotations.Mapper;
-import cn.iocoder.yudao.module.module.system.controller.admin.user.vo.*;
+import cn.iocoder.yudao.module.system.controller.admin.user.vo.*;
 
 /**
  * 用户 Mapper

+ 1 - 1
yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserMapper_xml

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
-<mapper namespace="cn.iocoder.yudao.module.module.system.dal.mysql.user.SystemUserMapper">
+<mapper namespace="cn.iocoder.yudao.module.system.dal.mysql.user.SystemUserMapper">
 
     <!--
         一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。

+ 1 - 1
yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserPageReqVO

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.module.system.controller.admin.user.vo;
+package cn.iocoder.yudao.module.system.controller.admin.user.vo;
 
 import lombok.*;
 import java.util.*;

+ 1 - 1
yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserRespVO

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.module.system.controller.admin.user.vo;
+package cn.iocoder.yudao.module.system.controller.admin.user.vo;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.*;

+ 3 - 3
yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserService

@@ -1,9 +1,9 @@
-package cn.iocoder.yudao.module.module.system.service.user;
+package cn.iocoder.yudao.module.system.service.user;
 
 import java.util.*;
 import javax.validation.*;
-import cn.iocoder.yudao.module.module.system.controller.admin.user.vo.*;
-import cn.iocoder.yudao.module.module.system.dal.dataobject.user.SystemUserDO;
+import cn.iocoder.yudao.module.system.controller.admin.user.vo.*;
+import cn.iocoder.yudao.module.system.dal.dataobject.user.SystemUserDO;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 
 /**

+ 6 - 6
yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserServiceImpl

@@ -1,19 +1,19 @@
-package cn.iocoder.yudao.module.module.system.service.user;
+package cn.iocoder.yudao.module.system.service.user;
 
 import org.springframework.stereotype.Service;
 import javax.annotation.Resource;
 import org.springframework.validation.annotation.Validated;
 
 import java.util.*;
-import cn.iocoder.yudao.module.module.system.controller.admin.user.vo.*;
-import cn.iocoder.yudao.module.module.system.dal.dataobject.user.SystemUserDO;
+import cn.iocoder.yudao.module.system.controller.admin.user.vo.*;
+import cn.iocoder.yudao.module.system.dal.dataobject.user.SystemUserDO;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 
-import cn.iocoder.yudao.module.module.system.convert.user.SystemUserConvert;
-import cn.iocoder.yudao.module.module.system.dal.mysql.user.SystemUserMapper;
+import cn.iocoder.yudao.module.system.convert.user.SystemUserConvert;
+import cn.iocoder.yudao.module.system.dal.mysql.user.SystemUserMapper;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.module.system.enums.ErrorCodeConstants.*;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.ListUtil;

+ 5 - 5
yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserServiceImplTest

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.module.system.service.user;
+package cn.iocoder.yudao.module.system.service.user;
 
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
@@ -8,9 +8,9 @@ import javax.annotation.Resource;
 
 import cn.iocoder.yudao.module.framework.test.core.ut.BaseDbUnitTest;
 
-import cn.iocoder.yudao.module.module.system.controller.admin.user.vo.*;
-import cn.iocoder.yudao.module.module.system.dal.dataobject.user.SystemUserDO;
-import cn.iocoder.yudao.module.module.system.dal.mysql.user.SystemUserMapper;
+import cn.iocoder.yudao.module.system.controller.admin.user.vo.*;
+import cn.iocoder.yudao.module.system.dal.dataobject.user.SystemUserDO;
+import cn.iocoder.yudao.module.system.dal.mysql.user.SystemUserMapper;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 
 import javax.annotation.Resource;
@@ -19,7 +19,7 @@ import java.util.*;
 import java.time.LocalDateTime;
 
 import static cn.hutool.core.util.RandomUtil.*;
-import static cn.iocoder.yudao.module.module.system.enums.ErrorCodeConstants.*;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
 import static cn.iocoder.yudao.module.framework.test.core.util.AssertUtils.*;
 import static cn.iocoder.yudao.module.framework.test.core.util.RandomUtils.*;
 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;

+ 1 - 1
yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_crud/java/SystemUserUpdateReqVO

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.module.system.controller.admin.user.vo;
+package cn.iocoder.yudao.module.system.controller.admin.user.vo;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.*;